Merge branch 'master' into docs
This commit is contained in:
commit
a75570a027
47 changed files with 1924 additions and 2342 deletions
|
|
@ -5,23 +5,25 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId "btools.routingapp"
|
||||
|
||||
versionCode 42
|
||||
versionCode 45
|
||||
versionName project.version
|
||||
|
||||
resValue('string', 'app_version', defaultConfig.versionName)
|
||||
setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName)
|
||||
|
||||
minSdkVersion 14
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
sourceSets.main.assets.srcDirs += new File(project.buildDir, 'assets')
|
||||
|
||||
if (project.hasProperty("RELEASE_STORE_FILE")) {
|
||||
if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) {
|
||||
signingConfigs {
|
||||
// this uses a file ~/.gradle/gradle.properties
|
||||
// with content:
|
||||
|
|
@ -49,7 +51,7 @@ android {
|
|||
release {
|
||||
minifyEnabled false
|
||||
debuggable false
|
||||
if (project.hasProperty("RELEASE_STORE_FILE")) {
|
||||
if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
|
@ -96,14 +98,21 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
|
||||
implementation 'androidx.work:work-runtime:2.7.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
|
||||
implementation project(':brouter-mapaccess')
|
||||
implementation project(':brouter-core')
|
||||
implementation project(':brouter-expressions')
|
||||
implementation project(':brouter-util')
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'androidx.work:work-testing:2.7.1'
|
||||
}
|
||||
|
||||
task generateProfiles(type: Exec) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BRouterActivityTest {
|
||||
@Rule
|
||||
public ActivityScenarioRule<BRouterActivity> rule = new ActivityScenarioRule<>(BRouterActivity.class);
|
||||
|
||||
@Test
|
||||
public void storageDirectories() {
|
||||
ActivityScenario<BRouterActivity> scenario = rule.getScenario();
|
||||
scenario.onActivity(activity -> {
|
||||
List<File> storageDirectories = activity.getStorageDirectories();
|
||||
|
||||
// Before Android Q (10) legacy storage access is possible
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
assertThat(storageDirectories, hasItem(Environment.getExternalStorageDirectory()));
|
||||
}
|
||||
|
||||
// When targeting older SDK we can access legacy storage on any android version
|
||||
if (activity.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.Q) {
|
||||
assertThat(storageDirectories, hasItem(Environment.getExternalStorageDirectory()));
|
||||
}
|
||||
|
||||
assertThat(storageDirectories, not(empty()));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ListenableWorker.Result;
|
||||
import androidx.work.testing.TestWorkerBuilder;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DownloadWorkerTest {
|
||||
private Context context;
|
||||
private Executor executor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadNewFile() {
|
||||
Data inputData = new Data.Builder()
|
||||
.putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"E105_N50"})
|
||||
.build();
|
||||
|
||||
DownloadWorker worker =
|
||||
TestWorkerBuilder.from(context, DownloadWorker.class, executor)
|
||||
.setInputData(inputData)
|
||||
.build();
|
||||
|
||||
Result result = worker.doWork();
|
||||
assertThat(result, is(Result.success()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadInvalidSegment() {
|
||||
Data inputData = new Data.Builder()
|
||||
.putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"X00"})
|
||||
.build();
|
||||
|
||||
DownloadWorker worker =
|
||||
TestWorkerBuilder.from(context, DownloadWorker.class, executor)
|
||||
.setInputData(inputData)
|
||||
.build();
|
||||
|
||||
Result result = worker.doWork();
|
||||
assertThat(result, is(Result.failure()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadNoSegments() {
|
||||
DownloadWorker worker =
|
||||
TestWorkerBuilder.from(context, DownloadWorker.class, executor)
|
||||
.build();
|
||||
|
||||
Result result = worker.doWork();
|
||||
assertThat(result, is(Result.failure()));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="btools.routingapp">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
|
@ -11,17 +13,16 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:allowBackup="false"
|
||||
android:preserveLegacyExternalStorage="true">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:preserveLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.App">
|
||||
<activity
|
||||
android:name=".BRouterActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:screenOrientation="unspecified"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
android:screenOrientation="unspecified">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
@ -29,20 +30,18 @@
|
|||
</activity>
|
||||
<activity
|
||||
android:name=".BInstallerActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"></activity>
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="landscape" />
|
||||
<activity
|
||||
android:name=".BImportActivity"
|
||||
android:label="Import Profile"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar">
|
||||
<!-- some apps (bluemail) do not recognize the .brf file extention, startactivity+intent is done for attachement with text/plain -->
|
||||
android:label="Import Profile">
|
||||
<!-- some apps (bluemail) do not recognize the .brf file extension, startactivity+intent is done for attachment with text/plain -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="text/plain" />
|
||||
|
|
@ -88,15 +87,10 @@
|
|||
</activity>
|
||||
|
||||
<service
|
||||
android:exported="true"
|
||||
android:name=".BRouterService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process=":brouter_service" />
|
||||
<service
|
||||
android:name="btools.routingapp.DownloadService"
|
||||
android:label="Download Service"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:enabled="true" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -13,4 +13,4 @@ profiles_url=https://brouter.de/brouter/profiles2/
|
|||
# these are comma separated arrays
|
||||
|
||||
check_lookup=lookups.dat
|
||||
check_profiles=car-eco.brf,car-eco-de.brf,car-eco-suspect_scan.brf,car-fast.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,fastbike-verylowtraffic.brf,hiking-beta.brf,moped.brf,rail.brf,river.brf,safety.brf,shortest.brf,trekking.brf,trekking-ignore-cr.brf,trekking-noferries.brf,trekking-nosteps.brf,trekking-steep.brf,vm-forum-liegerad-schnell.brf,vm-forum-velomobil-schnell.brf
|
||||
check_profiles=car-eco.brf,car-eco-de.brf,car-fast.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,fastbike-verylowtraffic.brf,hiking-mountain.brf,moped.brf,rail.brf,river.brf,safety.brf,shortest.brf,trekking.brf,trekking-ignore-cr.brf,trekking-noferries.brf,trekking-nosteps.brf,trekking-steep.brf,vm-forum-liegerad-schnell.brf,vm-forum-velomobil-schnell.brf
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
|
@ -12,6 +11,7 @@ import android.widget.EditText;
|
|||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
|
|
@ -20,7 +20,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class BImportActivity extends Activity {
|
||||
public class BImportActivity extends AppCompatActivity {
|
||||
// profile size is generally < 30 kb, so set max size to 100 kb
|
||||
private static final int MAX_PROFILE_SIZE = 100000;
|
||||
private EditText mTextFilename;
|
||||
|
|
@ -76,8 +76,8 @@ public class BImportActivity extends Activity {
|
|||
try (Cursor cursor = this.getContentResolver().query(intent.getData(), new String[]{
|
||||
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
filesize = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE));
|
||||
filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
|
||||
filesize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
|
||||
}
|
||||
}
|
||||
// is the file extention ".brf" in the file name
|
||||
|
|
|
|||
|
|
@ -1,112 +1,208 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import static btools.routingapp.BInstallerView.MASK_CURRENT_RD5;
|
||||
import static btools.routingapp.BInstallerView.MASK_DELETED_RD5;
|
||||
import static btools.routingapp.BInstallerView.MASK_INSTALLED_RD5;
|
||||
import static btools.routingapp.BInstallerView.MASK_SELECTED_RD5;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.os.StatFs;
|
||||
import android.util.Log;
|
||||
import android.text.format.Formatter;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class BInstallerActivity extends Activity {
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.WorkRequest;
|
||||
|
||||
public static final String DOWNLOAD_ACTION = "btools.routingapp.download";
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import btools.router.RoutingHelper;
|
||||
|
||||
public class BInstallerActivity extends AppCompatActivity {
|
||||
|
||||
private static final int DIALOG_CONFIRM_DELETE_ID = 1;
|
||||
|
||||
public static boolean downloadCanceled = false;
|
||||
private File mBaseDir;
|
||||
private BInstallerView mBInstallerView;
|
||||
private PowerManager mPowerManager;
|
||||
private WakeLock mWakeLock;
|
||||
private DownloadReceiver myReceiver;
|
||||
private Button mButtonDownload;
|
||||
private TextView mSummaryInfo;
|
||||
private LinearProgressIndicator mProgressIndicator;
|
||||
|
||||
public static long getAvailableSpace(String baseDir) {
|
||||
StatFs stat = new StatFs(baseDir);
|
||||
|
||||
public class DownloadReceiver extends BroadcastReceiver {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
|
||||
} else {
|
||||
//noinspection deprecation
|
||||
return (long) stat.getAvailableBlocks() * stat.getBlockSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.hasExtra("txt")) {
|
||||
String txt = intent.getStringExtra("txt");
|
||||
boolean ready = intent.getBooleanExtra("ready", false);
|
||||
mBInstallerView.setState(txt, ready);
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
|
||||
setContentView(R.layout.activity_binstaller);
|
||||
mSummaryInfo = findViewById(R.id.textViewSegmentSummary);
|
||||
mBInstallerView = findViewById(R.id.BInstallerView);
|
||||
mBInstallerView.setOnSelectListener(
|
||||
() -> {
|
||||
updateDownloadButton();
|
||||
}
|
||||
);
|
||||
mButtonDownload = findViewById(R.id.buttonDownload);
|
||||
mButtonDownload.setOnClickListener(
|
||||
view -> {
|
||||
if (mBInstallerView.getSelectedTiles(MASK_DELETED_RD5).size() > 0) {
|
||||
showConfirmDelete();
|
||||
} else if (mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5).size() > 0) {
|
||||
downloadSelectedTiles();
|
||||
} else {
|
||||
downloadInstalledTiles();
|
||||
}
|
||||
}
|
||||
);
|
||||
mProgressIndicator = findViewById(R.id.progressDownload);
|
||||
|
||||
mBaseDir = ConfigHelper.getBaseDir(this);
|
||||
scanExistingFiles();
|
||||
}
|
||||
|
||||
private String getSegmentsPlural(int count) {
|
||||
Resources res = getResources();
|
||||
return res.getQuantityString(R.plurals.numberOfSegments, count, count);
|
||||
}
|
||||
|
||||
private void updateDownloadButton() {
|
||||
final ArrayList<Integer> selectedTilesDownload = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5);
|
||||
final ArrayList<Integer> selectedTilesUpdate = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5);
|
||||
final ArrayList<Integer> selectedTilesDelete = mBInstallerView.getSelectedTiles(MASK_DELETED_RD5);
|
||||
mSummaryInfo.setText("");
|
||||
|
||||
if (selectedTilesDelete.size() > 0) {
|
||||
mButtonDownload.setText(getString(R.string.action_delete, getSegmentsPlural(selectedTilesDelete.size())));
|
||||
mButtonDownload.setEnabled(true);
|
||||
} else if (selectedTilesDownload.size() > 0) {
|
||||
long tileSize = 0;
|
||||
for (int tileIndex : selectedTilesDownload) {
|
||||
tileSize += BInstallerSizes.getRd5Size(tileIndex);
|
||||
}
|
||||
mButtonDownload.setText(getString(R.string.action_download, getSegmentsPlural(selectedTilesDownload.size())));
|
||||
mButtonDownload.setEnabled(true);
|
||||
mSummaryInfo.setText(getString(R.string.summary_segments, Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath()))));
|
||||
} else if (selectedTilesUpdate.size() > 0) {
|
||||
mButtonDownload.setText(getString(R.string.action_update, getSegmentsPlural(selectedTilesUpdate.size())));
|
||||
mButtonDownload.setEnabled(true);
|
||||
} else {
|
||||
mButtonDownload.setText(getString(R.string.action_select));
|
||||
mButtonDownload.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteRawTracks() {
|
||||
File modeDir = new File(mBaseDir, "brouter/modes");
|
||||
String[] fileNames = modeDir.list();
|
||||
if (fileNames == null) return;
|
||||
for (String fileName : fileNames) {
|
||||
if (fileName.endsWith("_rawtrack.dat")) {
|
||||
File f = new File(modeDir, fileName);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadAll(ArrayList<Integer> downloadList) {
|
||||
ArrayList<String> urlparts = new ArrayList<>();
|
||||
for (Integer i : downloadList) {
|
||||
urlparts.add(baseNameForTile(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
downloadCanceled = false;
|
||||
|
||||
// Get an instance of the PowerManager
|
||||
mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
Data inputData = new Data.Builder()
|
||||
.putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, urlparts.toArray(new String[0]))
|
||||
.build();
|
||||
|
||||
// Create a bright wake lock
|
||||
mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
|
||||
.getName());
|
||||
Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
WorkRequest downloadWorkRequest =
|
||||
new OneTimeWorkRequest.Builder(DownloadWorker.class)
|
||||
.setInputData(inputData)
|
||||
.setConstraints(constraints)
|
||||
.build();
|
||||
|
||||
// instantiate our simulation view and set it as the activity's content
|
||||
mBInstallerView = new BInstallerView(this);
|
||||
setContentView(mBInstallerView);
|
||||
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
|
||||
workManager.enqueue(downloadWorkRequest);
|
||||
|
||||
workManager
|
||||
.getWorkInfoByIdLiveData(downloadWorkRequest.getId())
|
||||
.observe(this, workInfo -> {
|
||||
if (workInfo != null) {
|
||||
if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
|
||||
Toast.makeText(this, "Download scheduled. Check internet connection if it doesn't start.", Toast.LENGTH_LONG).show();
|
||||
mProgressIndicator.hide();
|
||||
mProgressIndicator.setIndeterminate(true);
|
||||
mProgressIndicator.show();
|
||||
}
|
||||
|
||||
if (workInfo.getState() == WorkInfo.State.RUNNING) {
|
||||
Data progress = workInfo.getProgress();
|
||||
String segmentName = progress.getString(DownloadWorker.PROGRESS_SEGMENT_NAME);
|
||||
int percent = progress.getInt(DownloadWorker.PROGRESS_SEGMENT_PERCENT, 0);
|
||||
if (percent > 0) {
|
||||
mProgressIndicator.setIndeterminate(false);
|
||||
}
|
||||
mProgressIndicator.setProgress(percent);
|
||||
}
|
||||
|
||||
if (workInfo.getState().isFinished()) {
|
||||
String result;
|
||||
switch (workInfo.getState()) {
|
||||
case FAILED:
|
||||
result = "failed.";
|
||||
break;
|
||||
case CANCELLED:
|
||||
result = "cancelled";
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
result = "succeeded";
|
||||
break;
|
||||
default:
|
||||
result = "";
|
||||
}
|
||||
Toast.makeText(this, "Download " + result + ".", Toast.LENGTH_SHORT).show();
|
||||
mProgressIndicator.hide();
|
||||
scanExistingFiles();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
deleteRawTracks(); // invalidate raw-tracks after data update
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
/*
|
||||
* when the activity is resumed, we acquire a wake-lock so that the
|
||||
* screen stays on, since the user will likely not be fiddling with the
|
||||
* screen or buttons.
|
||||
*/
|
||||
mWakeLock.acquire();
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(DOWNLOAD_ACTION);
|
||||
|
||||
myReceiver = new DownloadReceiver();
|
||||
registerReceiver(myReceiver, filter);
|
||||
|
||||
// Start the download manager
|
||||
mBInstallerView.startInstaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
|
||||
super.onPause();
|
||||
|
||||
mWakeLock.release();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (myReceiver != null) unregisterReceiver(myReceiver);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
AlertDialog.Builder builder;
|
||||
switch (id) {
|
||||
|
|
@ -116,7 +212,7 @@ public class BInstallerActivity extends Activity {
|
|||
.setTitle("Confirm Delete")
|
||||
.setMessage("Really delete?").setPositiveButton("Yes", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
mBInstallerView.deleteSelectedTiles();
|
||||
deleteSelectedTiles();
|
||||
}
|
||||
}).setNegativeButton("No", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
|
|
@ -129,29 +225,76 @@ public class BInstallerActivity extends Activity {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void showConfirmDelete() {
|
||||
showDialog(DIALOG_CONFIRM_DELETE_ID);
|
||||
}
|
||||
|
||||
private Set<Integer> dialogIds = new HashSet<Integer>();
|
||||
private void scanExistingFiles() {
|
||||
mBInstallerView.clearAllTilesStatus(MASK_CURRENT_RD5 | MASK_INSTALLED_RD5 | MASK_DELETED_RD5 | MASK_SELECTED_RD5);
|
||||
|
||||
private void showNewDialog(int id) {
|
||||
if (dialogIds.contains(Integer.valueOf(id))) {
|
||||
removeDialog(id);
|
||||
scanExistingFiles(new File(mBaseDir, "brouter/segments4"));
|
||||
|
||||
File secondary = RoutingHelper.getSecondarySegmentDir(new File(mBaseDir, "brouter/segments4"));
|
||||
if (secondary != null) {
|
||||
scanExistingFiles(secondary);
|
||||
}
|
||||
dialogIds.add(Integer.valueOf(id));
|
||||
showDialog(id);
|
||||
}
|
||||
|
||||
private void scanExistingFiles(File dir) {
|
||||
String[] fileNames = dir.list();
|
||||
if (fileNames == null) return;
|
||||
String suffix = ".rd5";
|
||||
for (String fileName : fileNames) {
|
||||
if (fileName.endsWith(suffix)) {
|
||||
String basename = fileName.substring(0, fileName.length() - suffix.length());
|
||||
int tileIndex = tileForBaseName(basename);
|
||||
mBInstallerView.setTileStatus(tileIndex, MASK_INSTALLED_RD5);
|
||||
|
||||
static public long getAvailableSpace(String baseDir) {
|
||||
StatFs stat = new StatFs(baseDir);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
|
||||
} else {
|
||||
return stat.getAvailableBlocks() * stat.getBlockSize();
|
||||
long age = System.currentTimeMillis() - new File(dir, fileName).lastModified();
|
||||
if (age < 10800000) mBInstallerView.setTileStatus(tileIndex, MASK_CURRENT_RD5); // 3 hours
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteSelectedTiles() {
|
||||
ArrayList<Integer> selectedTiles = mBInstallerView.getSelectedTiles(MASK_DELETED_RD5);
|
||||
for (int tileIndex : selectedTiles) {
|
||||
new File(mBaseDir, "brouter/segments4/" + baseNameForTile(tileIndex) + ".rd5").delete();
|
||||
}
|
||||
scanExistingFiles();
|
||||
}
|
||||
|
||||
private void downloadSelectedTiles() {
|
||||
ArrayList<Integer> selectedTiles = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5);
|
||||
downloadAll(selectedTiles);
|
||||
mBInstallerView.clearAllTilesStatus(MASK_SELECTED_RD5);
|
||||
}
|
||||
|
||||
private void downloadInstalledTiles() {
|
||||
ArrayList<Integer> selectedTiles = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5);
|
||||
downloadAll(selectedTiles);
|
||||
}
|
||||
|
||||
private int tileForBaseName(String basename) {
|
||||
String uname = basename.toUpperCase(Locale.ROOT);
|
||||
int idx = uname.indexOf("_");
|
||||
if (idx < 0) return -1;
|
||||
String slon = uname.substring(0, idx);
|
||||
String slat = uname.substring(idx + 1);
|
||||
int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt(slon.substring(1)) :
|
||||
(slon.charAt(0) == 'E' ? Integer.parseInt(slon.substring(1)) : -1);
|
||||
int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt(slat.substring(1)) :
|
||||
(slat.charAt(0) == 'N' ? Integer.parseInt(slat.substring(1)) : -1);
|
||||
if (ilon < -180 || ilon >= 180 || ilon % 5 != 0) return -1;
|
||||
if (ilat < -90 || ilat >= 90 || ilat % 5 != 0) return -1;
|
||||
return (ilon + 180) / 5 + 72 * ((ilat + 90) / 5);
|
||||
}
|
||||
|
||||
private String baseNameForTile(int tileIndex) {
|
||||
int lon = (tileIndex % 72) * 5 - 180;
|
||||
int lat = (tileIndex / 72) * 5 - 90;
|
||||
String slon = lon < 0 ? "W" + (-lon) : "E" + lon;
|
||||
String slat = lat < 0 ? "S" + (-lat) : "N" + lat;
|
||||
return slon + "_" + slat;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
|
@ -20,262 +9,37 @@ import android.graphics.Canvas;
|
|||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager;
|
||||
import android.os.StatFs;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import btools.mapaccess.PhysicalFile;
|
||||
import btools.mapaccess.Rd5DiffManager;
|
||||
import btools.mapaccess.Rd5DiffTool;
|
||||
import btools.router.RoutingHelper;
|
||||
import btools.util.ProgressListener;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BInstallerView extends View {
|
||||
private static final int MASK_SELECTED_RD5 = 1;
|
||||
private static final int MASK_DELETED_RD5 = 2;
|
||||
private static final int MASK_INSTALLED_RD5 = 4;
|
||||
private static final int MASK_CURRENT_RD5 = 8;
|
||||
|
||||
private int imgwOrig;
|
||||
private int imghOrig;
|
||||
private float scaleOrig;
|
||||
|
||||
private int imgw;
|
||||
private int imgh;
|
||||
|
||||
private float lastDownX;
|
||||
private float lastDownY;
|
||||
|
||||
private Bitmap bmp;
|
||||
|
||||
public static final int MASK_SELECTED_RD5 = 1;
|
||||
public static final int MASK_DELETED_RD5 = 2;
|
||||
public static final int MASK_INSTALLED_RD5 = 4;
|
||||
public static final int MASK_CURRENT_RD5 = 8;
|
||||
private static final float SCALE_GRID_VISIBLE = 3;
|
||||
private final Bitmap bmp;
|
||||
private final float[] testVector = new float[2];
|
||||
private final int[] tileStatus;
|
||||
private final Matrix mat;
|
||||
private final GestureDetector mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleGestureDetector;
|
||||
Paint paintGrid = new Paint();
|
||||
Paint paintTiles = new Paint();
|
||||
private float viewscale;
|
||||
|
||||
private float[] testVector = new float[2];
|
||||
|
||||
private int[] tileStatus;
|
||||
|
||||
private boolean tilesVisible = false;
|
||||
private OnSelectListener mOnSelectListener;
|
||||
|
||||
private long availableSize;
|
||||
private File baseDir;
|
||||
private File segmentDir;
|
||||
public BInstallerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
private boolean isDownloading = false;
|
||||
public static boolean downloadCanceled = false;
|
||||
|
||||
private long currentDownloadSize;
|
||||
private String currentDownloadFile = "";
|
||||
private volatile String currentDownloadOperation = "";
|
||||
private String downloadAction = "";
|
||||
private volatile String newDownloadAction = "";
|
||||
|
||||
private long totalSize = 0;
|
||||
private long rd5Tiles = 0;
|
||||
private long delTiles = 0;
|
||||
|
||||
Paint pnt_1 = new Paint();
|
||||
Paint pnt_2 = new Paint();
|
||||
Paint paint = new Paint();
|
||||
|
||||
Activity mActivity;
|
||||
|
||||
|
||||
protected String baseNameForTile(int tileIndex) {
|
||||
int lon = (tileIndex % 72) * 5 - 180;
|
||||
int lat = (tileIndex / 72) * 5 - 90;
|
||||
String slon = lon < 0 ? "W" + (-lon) : "E" + lon;
|
||||
String slat = lat < 0 ? "S" + (-lat) : "N" + lat;
|
||||
return slon + "_" + slat;
|
||||
}
|
||||
|
||||
private int gridPos2Tileindex(int ix, int iy) {
|
||||
return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2);
|
||||
}
|
||||
|
||||
private int tileForBaseName(String basename) {
|
||||
String uname = basename.toUpperCase(Locale.ROOT);
|
||||
int idx = uname.indexOf("_");
|
||||
if (idx < 0) return -1;
|
||||
String slon = uname.substring(0, idx);
|
||||
String slat = uname.substring(idx + 1);
|
||||
int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt(slon.substring(1)) :
|
||||
(slon.charAt(0) == 'E' ? Integer.parseInt(slon.substring(1)) : -1);
|
||||
int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt(slat.substring(1)) :
|
||||
(slat.charAt(0) == 'N' ? Integer.parseInt(slat.substring(1)) : -1);
|
||||
if (ilon < -180 || ilon >= 180 || ilon % 5 != 0) return -1;
|
||||
if (ilat < -90 || ilat >= 90 || ilat % 5 != 0) return -1;
|
||||
return (ilon + 180) / 5 + 72 * ((ilat + 90) / 5);
|
||||
}
|
||||
|
||||
|
||||
public boolean isDownloadCanceled() {
|
||||
return downloadCanceled;
|
||||
}
|
||||
|
||||
private void toggleDownload() {
|
||||
if (isDownloading) {
|
||||
downloadCanceled = true;
|
||||
downloadAction = "Canceling...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (delTiles > 0) {
|
||||
((BInstallerActivity) getContext()).showConfirmDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
int tidx_min = -1;
|
||||
int min_size = Integer.MAX_VALUE;
|
||||
|
||||
ArrayList<Integer> downloadList = new ArrayList<>();
|
||||
// prepare download list
|
||||
for (int ix = 0; ix < 72; ix++) {
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tidx = gridPos2Tileindex(ix, iy);
|
||||
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
|
||||
int tilesize = BInstallerSizes.getRd5Size(tidx);
|
||||
downloadList.add(tidx);
|
||||
if (tilesize > 0 && tilesize < min_size) {
|
||||
tidx_min = tidx;
|
||||
min_size = tilesize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadList.size() > 0) {
|
||||
isDownloading = true;
|
||||
downloadAll(downloadList);
|
||||
for (Integer i : downloadList) {
|
||||
tileStatus[i.intValue()] ^= tileStatus[i.intValue()] & MASK_SELECTED_RD5;
|
||||
}
|
||||
downloadList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadAll(ArrayList<Integer> downloadList) {
|
||||
ArrayList<String> urlparts = new ArrayList<>();
|
||||
for (Integer i : downloadList) {
|
||||
urlparts.add(baseNameForTile(i.intValue()));
|
||||
}
|
||||
|
||||
currentDownloadOperation = "Start download ...";
|
||||
downloadAction = "";
|
||||
downloadCanceled = false;
|
||||
isDownloading = true;
|
||||
|
||||
//final DownloadBackground downloadTask = new DownloadBackground(getContext(), urlparts, baseDir);
|
||||
//downloadTask.execute( );
|
||||
Intent intent = new Intent(mActivity, DownloadService.class);
|
||||
intent.putExtra("dir", baseDir.getAbsolutePath() + "/brouter/");
|
||||
intent.putExtra("urlparts", urlparts);
|
||||
mActivity.startService(intent);
|
||||
|
||||
deleteRawTracks(); // invalidate raw-tracks after data update
|
||||
}
|
||||
|
||||
|
||||
public void downloadDone(boolean success) {
|
||||
isDownloading = false;
|
||||
if (success) {
|
||||
scanExistingFiles();
|
||||
toggleDownload(); // keep on if no error
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setState(String txt, boolean b) {
|
||||
currentDownloadOperation = txt;
|
||||
downloadAction = "";
|
||||
isDownloading = b;
|
||||
if (!b) {
|
||||
scanExistingFiles();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private int tileIndex(float x, float y) {
|
||||
int ix = (int) (72.f * x / bmp.getWidth());
|
||||
int iy = (int) (36.f * y / bmp.getHeight());
|
||||
if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy);
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void clearTileSelection(int mask) {
|
||||
// clear selection if zooming out
|
||||
for (int ix = 0; ix < 72; ix++)
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tidx = gridPos2Tileindex(ix, iy);
|
||||
tileStatus[tidx] ^= tileStatus[tidx] & mask;
|
||||
}
|
||||
}
|
||||
|
||||
// get back the current image scale
|
||||
private float currentScale() {
|
||||
testVector[1] = 1.f;
|
||||
mat.mapVectors(testVector);
|
||||
return testVector[1] / viewscale;
|
||||
}
|
||||
|
||||
private void deleteRawTracks() {
|
||||
File modeDir = new File(baseDir, "brouter/modes");
|
||||
String[] fileNames = modeDir.list();
|
||||
if (fileNames == null) return;
|
||||
for (String fileName : fileNames) {
|
||||
if (fileName.endsWith("_rawtrack.dat")) {
|
||||
File f = new File(modeDir, fileName);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scanExistingFiles() {
|
||||
clearTileSelection(MASK_INSTALLED_RD5 | MASK_CURRENT_RD5);
|
||||
|
||||
scanExistingFiles(new File(baseDir, "brouter/segments4"));
|
||||
|
||||
File secondary = RoutingHelper.getSecondarySegmentDir(new File(baseDir, "brouter/segments4"));
|
||||
if (secondary != null) {
|
||||
scanExistingFiles(secondary);
|
||||
}
|
||||
|
||||
availableSize = -1;
|
||||
try {
|
||||
availableSize = (long) ((BInstallerActivity) getContext()).getAvailableSpace(baseDir.getAbsolutePath());
|
||||
//StatFs stat = new StatFs(baseDir.getAbsolutePath ());
|
||||
//availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong();
|
||||
} catch (Exception e) { /* ignore */ }
|
||||
}
|
||||
|
||||
private void scanExistingFiles(File dir) {
|
||||
String[] fileNames = dir.list();
|
||||
if (fileNames == null) return;
|
||||
String suffix = ".rd5";
|
||||
for (String fileName : fileNames) {
|
||||
if (fileName.endsWith(suffix)) {
|
||||
String basename = fileName.substring(0, fileName.length() - suffix.length());
|
||||
int tidx = tileForBaseName(basename);
|
||||
tileStatus[tidx] |= MASK_INSTALLED_RD5;
|
||||
|
||||
long age = System.currentTimeMillis() - new File(dir, fileName).lastModified();
|
||||
if (age < 10800000) tileStatus[tidx] |= MASK_CURRENT_RD5; // 3 hours
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Matrix mat;
|
||||
private Matrix matText;
|
||||
|
||||
public void startInstaller() {
|
||||
|
||||
baseDir = ConfigHelper.getBaseDir(getContext());
|
||||
segmentDir = new File(baseDir, "brouter/segments4");
|
||||
try {
|
||||
AssetManager assetManager = getContext().getAssets();
|
||||
InputStream istr = assetManager.open("world.png");
|
||||
|
|
@ -286,56 +50,111 @@ public class BInstallerView extends View {
|
|||
}
|
||||
|
||||
tileStatus = new int[72 * 36];
|
||||
scanExistingFiles();
|
||||
|
||||
float scaleX = imgwOrig / ((float) bmp.getWidth());
|
||||
float scaley = imghOrig / ((float) bmp.getHeight());
|
||||
|
||||
viewscale = scaleX < scaley ? scaleX : scaley;
|
||||
|
||||
mat = new Matrix();
|
||||
mat.postScale(viewscale, viewscale);
|
||||
tilesVisible = false;
|
||||
mGestureDetector = new GestureDetector(context, new GestureListener());
|
||||
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
|
||||
}
|
||||
|
||||
public BInstallerView(Context context) {
|
||||
super(context);
|
||||
mActivity = (Activity) context;
|
||||
public void setOnSelectListener(OnSelectListener listener) {
|
||||
mOnSelectListener = listener;
|
||||
}
|
||||
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
imgwOrig = metrics.widthPixels;
|
||||
imghOrig = metrics.heightPixels;
|
||||
int im = imgwOrig > imghOrig ? imgwOrig : imghOrig;
|
||||
private void setRatio(float ratio, float focusX, float focusY) {
|
||||
if (currentScale() * ratio >= 1) {
|
||||
mat.postScale(ratio, ratio, focusX, focusY);
|
||||
fitBounds();
|
||||
tilesVisible = currentScale() >= SCALE_GRID_VISIBLE;
|
||||
|
||||
scaleOrig = im / 480.f;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
matText = new Matrix();
|
||||
matText.preScale(scaleOrig, scaleOrig);
|
||||
private void setScale(float scale, float focusX, float focusY) {
|
||||
float ratio = scale / currentScale();
|
||||
setRatio(ratio, focusX, focusY);
|
||||
}
|
||||
|
||||
imgw = (int) (imgwOrig / scaleOrig);
|
||||
imgh = (int) (imghOrig / scaleOrig);
|
||||
public void setTileStatus(int tileIndex, int tileMask) {
|
||||
tileStatus[tileIndex] |= tileMask;
|
||||
if (mOnSelectListener != null) {
|
||||
mOnSelectListener.onSelect();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
totalSize = 0;
|
||||
rd5Tiles = 0;
|
||||
delTiles = 0;
|
||||
public void toggleTileStatus(int tileIndex, int tileMask) {
|
||||
tileStatus[tileIndex] ^= tileMask;
|
||||
if (mOnSelectListener != null) {
|
||||
mOnSelectListener.onSelect();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void clearAllTilesStatus(int tileMask) {
|
||||
for (int ix = 0; ix < 72; ix++) {
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tileIndex = gridPos2Tileindex(ix, iy);
|
||||
tileStatus[tileIndex] ^= tileStatus[tileIndex] & tileMask;
|
||||
}
|
||||
}
|
||||
if (mOnSelectListener != null) {
|
||||
mOnSelectListener.onSelect();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public ArrayList<Integer> getSelectedTiles(int tileMask) {
|
||||
ArrayList<Integer> selectedTiles = new ArrayList<>();
|
||||
for (int ix = 0; ix < 72; ix++) {
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tileIndex = gridPos2Tileindex(ix, iy);
|
||||
if ((tileStatus[tileIndex] & tileMask) != 0 && BInstallerSizes.getRd5Size(tileIndex) > 0) {
|
||||
selectedTiles.add(tileIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedTiles;
|
||||
}
|
||||
|
||||
private int gridPos2Tileindex(int ix, int iy) {
|
||||
return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2);
|
||||
}
|
||||
|
||||
private int tileIndex(float x, float y) {
|
||||
int ix = (int) (72.f * x / bmp.getWidth());
|
||||
int iy = (int) (36.f * y / bmp.getHeight());
|
||||
if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// get back the current image scale
|
||||
private float currentScale() {
|
||||
testVector[0] = 1.f;
|
||||
mat.mapVectors(testVector);
|
||||
return testVector[0] / viewscale;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
}
|
||||
|
||||
private void toast(String msg) {
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
|
||||
int imgwOrig = getWidth();
|
||||
int imghOrig = getHeight();
|
||||
|
||||
float scaleX = imgwOrig / ((float) bmp.getWidth());
|
||||
float scaleY = imghOrig / ((float) bmp.getHeight());
|
||||
|
||||
viewscale = Math.max(scaleX, scaleY);
|
||||
|
||||
mat.postScale(viewscale, viewscale);
|
||||
tilesVisible = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (!isDownloading) {
|
||||
canvas.setMatrix(mat);
|
||||
canvas.drawBitmap(bmp, 0, 0, null);
|
||||
}
|
||||
canvas.setMatrix(mat);
|
||||
canvas.drawBitmap(bmp, 0, 0, null);
|
||||
// draw 5*5 lattice starting at scale=3
|
||||
|
||||
int iw = bmp.getWidth();
|
||||
|
|
@ -343,290 +162,164 @@ public class BInstallerView extends View {
|
|||
float fw = iw / 72.f;
|
||||
float fh = ih / 36.f;
|
||||
|
||||
boolean drawGrid = tilesVisible && !isDownloading;
|
||||
|
||||
if (drawGrid) {
|
||||
|
||||
pnt_1.setColor(Color.GREEN);
|
||||
|
||||
for (int ix = 1; ix < 72; ix++) {
|
||||
float fx = fw * ix;
|
||||
canvas.drawLine(fx, 0, fx, ih, pnt_1);
|
||||
if (tilesVisible) {
|
||||
paintGrid.setColor(Color.GREEN);
|
||||
paintGrid.setStyle(Paint.Style.STROKE);
|
||||
for (int ix = 0; ix < 72; ix++) {
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tidx = gridPos2Tileindex(ix, iy);
|
||||
int tilesize = BInstallerSizes.getRd5Size(tidx);
|
||||
if (tilesize > 0) {
|
||||
canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, paintGrid);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int iy = 1; iy < 36; iy++) {
|
||||
float fy = fh * iy;
|
||||
canvas.drawLine(0, fy, iw, fy, pnt_1);
|
||||
}
|
||||
}
|
||||
rd5Tiles = 0;
|
||||
delTiles = 0;
|
||||
totalSize = 0;
|
||||
int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5;
|
||||
int mask3 = mask2 | MASK_CURRENT_RD5;
|
||||
|
||||
pnt_2.setColor(Color.GRAY);
|
||||
pnt_2.setStrokeWidth(1);
|
||||
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5, mask3, false, false, drawGrid);
|
||||
pnt_2.setColor(Color.BLUE);
|
||||
pnt_2.setStrokeWidth(1);
|
||||
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3, false, false, drawGrid);
|
||||
pnt_2.setColor(Color.GREEN);
|
||||
pnt_2.setStrokeWidth(2);
|
||||
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_SELECTED_RD5, mask2, true, false, drawGrid);
|
||||
pnt_2.setColor(Color.YELLOW);
|
||||
pnt_2.setStrokeWidth(2);
|
||||
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2, true, false, drawGrid);
|
||||
pnt_2.setColor(Color.RED);
|
||||
pnt_2.setStrokeWidth(2);
|
||||
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2, false, true, drawGrid);
|
||||
int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5;
|
||||
int mask3 = mask2 | MASK_CURRENT_RD5;
|
||||
|
||||
canvas.setMatrix(matText);
|
||||
|
||||
paint.setColor(Color.RED);
|
||||
|
||||
long mb = 1024 * 1024;
|
||||
|
||||
if (isDownloading) {
|
||||
String sizeHint = currentDownloadSize > 0 ? " (" + ((currentDownloadSize + mb - 1) / mb) + " MB)" : "";
|
||||
paint.setTextSize(30);
|
||||
canvas.drawText(currentDownloadOperation, 30, (imgh / 3) * 2 - 30, paint);
|
||||
// canvas.drawText( currentDownloadOperation + " " + currentDownloadFile + sizeHint, 30, (imgh/3)*2-30, paint);
|
||||
canvas.drawText(downloadAction, 30, (imgh / 3) * 2, paint);
|
||||
}
|
||||
if (!tilesVisible && !isDownloading) {
|
||||
paint.setTextSize(35);
|
||||
canvas.drawText("Touch region to zoom in!", 30, (imgh / 3) * 2, paint);
|
||||
}
|
||||
paint.setTextSize(20);
|
||||
|
||||
|
||||
String totmb = ((totalSize + mb - 1) / mb) + " MB";
|
||||
String freemb = availableSize >= 0 ? ((availableSize + mb - 1) / mb) + " MB" : "?";
|
||||
canvas.drawText("Selected segments=" + rd5Tiles, 10, 25, paint);
|
||||
canvas.drawText("Size=" + totmb + " Free=" + freemb, 10, 45, paint);
|
||||
|
||||
|
||||
String btnText = null;
|
||||
if (isDownloading) btnText = "Cancel Download";
|
||||
else if (delTiles > 0) btnText = "Delete " + delTiles + " tiles";
|
||||
else if (rd5Tiles > 0) btnText = "Start Download";
|
||||
else if (tilesVisible &&
|
||||
rd5Tiles == 0 &&
|
||||
RoutingHelper.hasDirectoryAnyDatafiles(segmentDir)) btnText = "Update all";
|
||||
|
||||
if (btnText != null) {
|
||||
canvas.drawLine(imgw - btnw, imgh - btnh, imgw - btnw, imgh - 2, paint);
|
||||
canvas.drawLine(imgw - btnw, imgh - btnh, imgw - 2, imgh - btnh, paint);
|
||||
canvas.drawLine(imgw - btnw, imgh - btnh, imgw - btnw, imgh - 2, paint);
|
||||
canvas.drawLine(imgw - 2, imgh - btnh, imgw - 2, imgh - 2, paint);
|
||||
canvas.drawLine(imgw - btnw, imgh - 2, imgw - 2, imgh - 2, paint);
|
||||
canvas.drawText(btnText, imgw - btnw + 5, imgh - 10, paint);
|
||||
paintTiles.setStyle(Paint.Style.STROKE);
|
||||
paintTiles.setColor(Color.GRAY);
|
||||
paintTiles.setStrokeWidth(1);
|
||||
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5, mask3);
|
||||
paintTiles.setColor(Color.BLUE);
|
||||
paintTiles.setStrokeWidth(1);
|
||||
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3);
|
||||
paintTiles.setColor(Color.GREEN);
|
||||
paintTiles.setStrokeWidth(2);
|
||||
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5, mask2);
|
||||
paintTiles.setColor(Color.YELLOW);
|
||||
paintTiles.setStrokeWidth(2);
|
||||
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2);
|
||||
paintTiles.setColor(Color.RED);
|
||||
paintTiles.setStrokeWidth(2);
|
||||
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2);
|
||||
}
|
||||
}
|
||||
|
||||
int btnh = 40;
|
||||
int btnw = 160;
|
||||
|
||||
|
||||
float tx, ty;
|
||||
|
||||
private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask, boolean doCount, boolean cntDel, boolean doDraw) {
|
||||
private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask) {
|
||||
for (int ix = 0; ix < 72; ix++)
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tidx = gridPos2Tileindex(ix, iy);
|
||||
if ((tileStatus[tidx] & mask) == status) {
|
||||
int tilesize = BInstallerSizes.getRd5Size(tidx);
|
||||
if (tilesize > 0) {
|
||||
if (doCount) {
|
||||
rd5Tiles++;
|
||||
totalSize += BInstallerSizes.getRd5Size(tidx);
|
||||
}
|
||||
if (cntDel) {
|
||||
delTiles++;
|
||||
totalSize += BInstallerSizes.getRd5Size(tidx);
|
||||
}
|
||||
if (!doDraw)
|
||||
continue;
|
||||
// draw cross
|
||||
canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), pnt);
|
||||
canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt);
|
||||
|
||||
// draw frame
|
||||
canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * iy, pnt);
|
||||
canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * (iy + 1), pnt);
|
||||
canvas.drawLine(fw * ix, fh * iy, fw * ix, fh * (iy + 1), pnt);
|
||||
canvas.drawLine(fw * (ix + 1), fh * iy, fw * (ix + 1), fh * (iy + 1), pnt);
|
||||
canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteSelectedTiles() {
|
||||
for (int ix = 0; ix < 72; ix++) {
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tidx = gridPos2Tileindex(ix, iy);
|
||||
if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) {
|
||||
new File(baseDir, "brouter/segments4/" + baseNameForTile(tidx) + ".rd5").delete();
|
||||
}
|
||||
}
|
||||
private void fitBounds() {
|
||||
float[] srcPoints = new float[]{
|
||||
0, 0,
|
||||
bmp.getWidth(), bmp.getHeight()
|
||||
};
|
||||
float[] dstPoints = new float[srcPoints.length];
|
||||
float transX = 0;
|
||||
float transY = 0;
|
||||
mat.mapPoints(dstPoints, srcPoints);
|
||||
if (dstPoints[0] > 0) {
|
||||
transX = -dstPoints[0];
|
||||
} else if (dstPoints[2] < getWidth()) {
|
||||
transX = getWidth() - dstPoints[2];
|
||||
}
|
||||
if (dstPoints[1] > 0) {
|
||||
transY = -dstPoints[1];
|
||||
} else if (dstPoints[3] < getHeight()) {
|
||||
transY = getHeight() - dstPoints[3];
|
||||
}
|
||||
if (transX != 0 || transY != 0) {
|
||||
mat.postTranslate(transX, transY);
|
||||
}
|
||||
scanExistingFiles();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
|
||||
// get pointer index from the event object
|
||||
int pointerIndex = event.getActionIndex();
|
||||
|
||||
// get pointer ID
|
||||
int pointerId = event.getPointerId(pointerIndex);
|
||||
|
||||
// get masked (not specific to a pointer) action
|
||||
int maskedAction = event.getActionMasked();
|
||||
|
||||
switch (maskedAction) {
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
lastDownX = event.getX();
|
||||
lastDownY = event.getY();
|
||||
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: { // a pointer was moved
|
||||
|
||||
if (isDownloading) break;
|
||||
int np = event.getPointerCount();
|
||||
int nh = event.getHistorySize();
|
||||
if (nh == 0) break;
|
||||
|
||||
float x0 = event.getX(0);
|
||||
float y0 = event.getY(0);
|
||||
float hx0 = event.getHistoricalX(0, 0);
|
||||
float hy0 = event.getHistoricalY(0, 0);
|
||||
|
||||
if (np > 1) // multi-touch
|
||||
{
|
||||
float x1 = event.getX(1);
|
||||
float y1 = event.getY(1);
|
||||
float hx1 = event.getHistoricalX(1, 0);
|
||||
float hy1 = event.getHistoricalY(1, 0);
|
||||
|
||||
float r = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
|
||||
float hr = (float) Math.sqrt((hx1 - hx0) * (hx1 - hx0) + (hy1 - hy0) * (hy1 - hy0));
|
||||
|
||||
if (hr > 0.) {
|
||||
float ratio = r / hr;
|
||||
|
||||
float mx = (x1 + x0) / 2.f;
|
||||
float my = (y1 + y0) / 2.f;
|
||||
|
||||
float scale = currentScale();
|
||||
float newscale = scale * ratio;
|
||||
|
||||
if (newscale > 10.f) ratio *= (10.f / newscale);
|
||||
if (newscale < 0.5f) ratio *= (0.5f / newscale);
|
||||
|
||||
mat.postScale(ratio, ratio, mx, my);
|
||||
|
||||
mat.postScale(ratio, ratio, mx, my);
|
||||
|
||||
boolean tilesv = currentScale() >= 3.f;
|
||||
if (tilesVisible && !tilesv) {
|
||||
clearTileSelection(MASK_SELECTED_RD5 | MASK_DELETED_RD5);
|
||||
}
|
||||
tilesVisible = tilesv;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
mat.postTranslate(x0 - hx0, y0 - hy0);
|
||||
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
|
||||
long downTime = event.getEventTime() - event.getDownTime();
|
||||
|
||||
if (downTime < 5 || downTime > 500) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (Math.abs(lastDownX - event.getX()) > 10 || Math.abs(lastDownY - event.getY()) > 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
// download button?
|
||||
if ((delTiles > 0 || rd5Tiles >= 0 || isDownloading) && event.getX() > imgwOrig - btnw * scaleOrig && event.getY() > imghOrig - btnh * scaleOrig) {
|
||||
if (rd5Tiles == 0) {
|
||||
for (int ix = 0; ix < 72; ix++) {
|
||||
for (int iy = 0; iy < 36; iy++) {
|
||||
int tidx = gridPos2Tileindex(ix, iy);
|
||||
if (tidx != -1) {
|
||||
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
|
||||
tileStatus[tidx] |= MASK_SELECTED_RD5;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
toggleDownload();
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tilesVisible) {
|
||||
float scale = currentScale();
|
||||
if (scale > 0f && scale < 5f) {
|
||||
float ratio = 5f / scale;
|
||||
mat.postScale(ratio, ratio, event.getX(), event.getY());
|
||||
tilesVisible = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (isDownloading) break;
|
||||
|
||||
Matrix imat = new Matrix();
|
||||
if (mat.invert(imat)) {
|
||||
float[] touchpoint = new float[2];
|
||||
touchpoint[0] = event.getX();
|
||||
touchpoint[1] = event.getY();
|
||||
imat.mapPoints(touchpoint);
|
||||
|
||||
int tidx = tileIndex(touchpoint[0], touchpoint[1]);
|
||||
if (tidx != -1) {
|
||||
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
|
||||
tileStatus[tidx] ^= MASK_SELECTED_RD5;
|
||||
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
|
||||
tileStatus[tidx] |= MASK_DELETED_RD5;
|
||||
}
|
||||
} else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) {
|
||||
tileStatus[tidx] ^= MASK_DELETED_RD5;
|
||||
} else {
|
||||
tileStatus[tidx] ^= MASK_SELECTED_RD5;
|
||||
}
|
||||
}
|
||||
|
||||
tx = touchpoint[0];
|
||||
ty = touchpoint[1];
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
// TODO use data
|
||||
break;
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
|
||||
return true;
|
||||
boolean retVal = mScaleGestureDetector.onTouchEvent(event);
|
||||
retVal = mGestureDetector.onTouchEvent(event) || retVal;
|
||||
return retVal || super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
interface OnSelectListener {
|
||||
void onSelect();
|
||||
}
|
||||
|
||||
class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
if (tilesVisible) {
|
||||
Matrix imat = new Matrix();
|
||||
if (mat.invert(imat)) {
|
||||
float[] touchPoint = {e.getX(), e.getY()};
|
||||
imat.mapPoints(touchPoint);
|
||||
|
||||
int tidx = tileIndex(touchPoint[0], touchPoint[1]);
|
||||
if (tidx != -1) {
|
||||
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
|
||||
toggleTileStatus(tidx, MASK_SELECTED_RD5);
|
||||
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
|
||||
setTileStatus(tidx, MASK_DELETED_RD5);
|
||||
}
|
||||
} else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) {
|
||||
toggleTileStatus(tidx, MASK_DELETED_RD5);
|
||||
} else {
|
||||
toggleTileStatus(tidx, MASK_SELECTED_RD5);
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
if (!tilesVisible) {
|
||||
setScale(5, e.getX(), e.getY());
|
||||
} else {
|
||||
setScale(1, e.getX(), e.getY());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
mat.postTranslate(-distanceX, -distanceY);
|
||||
fitBounds();
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
|
||||
float focusX = scaleGestureDetector.getFocusX();
|
||||
float focusY = scaleGestureDetector.getFocusY();
|
||||
float ratio = scaleGestureDetector.getScaleFactor();
|
||||
|
||||
setRatio(ratio, focusX, focusY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,24 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.os.StatFs;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.os.EnvironmentCompat;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -9,36 +26,9 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.os.StatFs;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.EditText;
|
||||
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.os.EnvironmentCompat;
|
||||
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
public class BRouterActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
public class BRouterActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
|
||||
private static final int DIALOG_SELECTPROFILE_ID = 1;
|
||||
private static final int DIALOG_EXCEPTION_ID = 2;
|
||||
|
|
@ -129,11 +119,11 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques
|
|||
.setMessage(
|
||||
"*** Attention: ***\n\n" + "The Download Manager is used to download routing-data "
|
||||
+ "files which can be up to 170MB each. Do not start the Download Manager " + "on a cellular data connection without a data plan! "
|
||||
+ "Download speed is restricted to 4 MBit/s.").setPositiveButton("I know", new DialogInterface.OnClickListener() {
|
||||
+ "Download speed is restricted to 16 MBit/s.").setPositiveButton("I know", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class);
|
||||
startActivity(intent);
|
||||
// finish();
|
||||
showNewDialog(DIALOG_MAINACTION_ID);
|
||||
}
|
||||
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
|
|
@ -391,39 +381,12 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques
|
|||
|
||||
private String maptoolDirCandidate;
|
||||
|
||||
public boolean isOnline(Context context) {
|
||||
boolean result = false;
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Network nw = connectivityManager.getActiveNetwork();
|
||||
if (nw == null) return false;
|
||||
NetworkCapabilities nwc = connectivityManager.getNetworkCapabilities(nw);
|
||||
if (nwc == null) return false;
|
||||
result = nwc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) |
|
||||
nwc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) |
|
||||
nwc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||
|
||||
} else {
|
||||
NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
|
||||
if (ni == null) return false;
|
||||
result = ni.getType() == ConnectivityManager.TYPE_WIFI ||
|
||||
ni.getType() == ConnectivityManager.TYPE_MOBILE ||
|
||||
ni.getType() == ConnectivityManager.TYPE_ETHERNET;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectProfile(String[] items) {
|
||||
availableProfiles = items;
|
||||
|
||||
// if we have internet access, first show the main action dialog
|
||||
if (isOnline(this)) {
|
||||
showDialog(DIALOG_MAINACTION_ID);
|
||||
} else {
|
||||
showDialog(DIALOG_SELECTPROFILE_ID);
|
||||
}
|
||||
// show main dialog
|
||||
showDialog(DIALOG_MAINACTION_ID);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -626,6 +589,7 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques
|
|||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == 0) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
mBRouterView.startSetup(null, true);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,21 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
|
|
@ -12,36 +28,13 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeMap;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import btools.expressions.BExpressionContextWay;
|
||||
import btools.expressions.BExpressionMetaData;
|
||||
import btools.mapaccess.OsmNode;
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
|
@ -108,11 +101,6 @@ public class BRouterView extends View {
|
|||
|
||||
public void init() {
|
||||
try {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
imgw = metrics.widthPixels;
|
||||
imgh = metrics.heightPixels;
|
||||
|
||||
// get base dir from private file
|
||||
File baseDir = ConfigHelper.getBaseDir(getContext());
|
||||
// check if valid
|
||||
|
|
@ -123,6 +111,15 @@ public class BRouterView extends View {
|
|||
if (brd.isDirectory()) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q &&
|
||||
!brd.getAbsolutePath().contains("/Android/media/btools.routingapp")) {
|
||||
|
||||
// don't ask twice
|
||||
String version = "v" + getContext().getString(R.string.app_version);
|
||||
File vFile = new File(brd, "profiles2/"+version );
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
|
||||
&& vFile.exists()) {
|
||||
startSetup(baseDir, false);
|
||||
return;
|
||||
}
|
||||
String message = "(previous basedir " + baseDir + " has to migrate )";
|
||||
|
||||
((BRouterActivity) getContext()).selectBasedir(((BRouterActivity) getContext()).getStorageDirectories(), guessBaseDir(), message);
|
||||
|
|
@ -204,7 +201,9 @@ public class BRouterView extends View {
|
|||
|
||||
// new init is done move old files
|
||||
if (waitingForMigration) {
|
||||
moveFolders(oldMigrationPath, basedir + "/brouter");
|
||||
Log.d("BR", "path " + oldMigrationPath + " " + basedir);
|
||||
if (!oldMigrationPath.equals(basedir + "/brouter"))
|
||||
moveFolders(oldMigrationPath, basedir + "/brouter");
|
||||
waitingForMigration = false;
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +253,7 @@ public class BRouterView extends View {
|
|||
}
|
||||
}
|
||||
if (tracksDir == null) {
|
||||
tracksDir = new File(basedir, "router"); // fallback
|
||||
tracksDir = new File(basedir, "brouter"); // fallback
|
||||
}
|
||||
|
||||
String[] fileNames = profileDir.list();
|
||||
|
|
@ -569,7 +568,7 @@ public class BRouterView extends View {
|
|||
// for profile remote, use ref-track logic same as service interface
|
||||
rc.rawTrackPath = rawTrackPath;
|
||||
|
||||
cr = new RoutingEngine(tracksDir + "/brouter", null, segmentDir, wpList, rc);
|
||||
cr = new RoutingEngine(tracksDir.getAbsolutePath()+"/brouter", null, segmentDir, wpList, rc);
|
||||
cr.start();
|
||||
invalidate();
|
||||
|
||||
|
|
@ -606,16 +605,18 @@ public class BRouterView extends View {
|
|||
break;
|
||||
String name = ze.getName();
|
||||
File outfile = new File(path, name);
|
||||
outfile.getParentFile().mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(outfile);
|
||||
if (!outfile.exists()) {
|
||||
outfile.getParentFile().mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(outfile);
|
||||
|
||||
for (; ; ) {
|
||||
int len = zis.read(data, 0, 1024);
|
||||
if (len < 0)
|
||||
break;
|
||||
fos.write(data, 0, len);
|
||||
for (; ; ) {
|
||||
int len = zis.read(data, 0, 1024);
|
||||
if (len < 0)
|
||||
break;
|
||||
fos.write(data, 0, len);
|
||||
}
|
||||
fos.close();
|
||||
}
|
||||
fos.close();
|
||||
}
|
||||
is.close();
|
||||
return true;
|
||||
|
|
@ -690,6 +691,8 @@ public class BRouterView extends View {
|
|||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
imgw = w;
|
||||
imgh = h;
|
||||
}
|
||||
|
||||
private void toast(String msg) {
|
||||
|
|
|
|||
|
|
@ -1,487 +0,0 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.TrafficStats;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import btools.mapaccess.PhysicalFile;
|
||||
import btools.mapaccess.Rd5DiffManager;
|
||||
import btools.mapaccess.Rd5DiffTool;
|
||||
import btools.util.ProgressListener;
|
||||
|
||||
public class DownloadService extends Service implements ProgressListener {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private ServerConfig mServerConfig;
|
||||
|
||||
private NotificationHelper mNotificationHelper;
|
||||
private List<String> mUrlList;
|
||||
private String baseDir;
|
||||
|
||||
private volatile String newDownloadAction = "";
|
||||
private volatile String currentDownloadOperation = "";
|
||||
private long availableSize;
|
||||
|
||||
private Looper mServiceLooper;
|
||||
private ServiceHandler mServiceHandler;
|
||||
private NotificationManager mNM;
|
||||
String downloadUrl;
|
||||
public static boolean serviceState = false;
|
||||
private boolean bIsDownloading;
|
||||
|
||||
// Handler that receives messages from the thread
|
||||
private final class ServiceHandler extends Handler {
|
||||
public ServiceHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
bIsDownloading = true;
|
||||
downloadFiles();
|
||||
|
||||
stopForeground(true);
|
||||
stopSelf(msg.arg1);
|
||||
mNotificationHelper.stopNotification();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (DEBUG) Log.d("SERVICE", "onCreate");
|
||||
serviceState = true;
|
||||
mServerConfig = new ServerConfig(getApplicationContext());
|
||||
|
||||
HandlerThread thread = new HandlerThread("ServiceStartArguments", 1);
|
||||
thread.start();
|
||||
|
||||
// Get the HandlerThread's Looper and use it for our Handler
|
||||
mServiceLooper = thread.getLooper();
|
||||
mServiceHandler = new ServiceHandler(mServiceLooper);
|
||||
|
||||
availableSize = -1;
|
||||
try {
|
||||
availableSize = BInstallerActivity.getAvailableSpace(baseDir);
|
||||
//StatFs stat = new StatFs(baseDir);
|
||||
//availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong();
|
||||
} catch (Exception e) { /* ignore */ }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (DEBUG) Log.d("SERVICE", "onStartCommand");
|
||||
|
||||
mNotificationHelper = new NotificationHelper(this);
|
||||
Bundle extra = intent.getExtras();
|
||||
if (extra != null) {
|
||||
String dir = extra.getString("dir");
|
||||
List<String> urlparts = extra.getStringArrayList("urlparts");
|
||||
mUrlList = urlparts;
|
||||
baseDir = dir;
|
||||
}
|
||||
|
||||
mNotificationHelper.startNotification(this);
|
||||
|
||||
Message msg = mServiceHandler.obtainMessage();
|
||||
msg.arg1 = startId;
|
||||
mServiceHandler.sendMessage(msg);
|
||||
|
||||
// If we get killed, after returning from here, restart
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (DEBUG) Log.d("SERVICE", "onDestroy");
|
||||
serviceState = false;
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void downloadFiles() {
|
||||
|
||||
// first check lookup table and profiles
|
||||
String result = checkScripts();
|
||||
if (result != null) {
|
||||
if (DEBUG) Log.d("BR", "error: " + result);
|
||||
bIsDownloading = false;
|
||||
updateProgress("finished ");
|
||||
|
||||
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int count = 1;
|
||||
int size = mUrlList.size();
|
||||
for (String part : mUrlList) {
|
||||
String url = mServerConfig.getSegmentUrl() + part + ".rd5";
|
||||
if (DEBUG) Log.d("BR", "downlaod " + url);
|
||||
|
||||
result = download(count, size, url);
|
||||
if (result != null) {
|
||||
if (DEBUG) Log.d("BR", "" + result);
|
||||
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
} else {
|
||||
updateProgress("Download " + part + " " + count + "/" + size + " finshed");
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
bIsDownloading = false;
|
||||
updateProgress("finished ");
|
||||
}
|
||||
|
||||
|
||||
public void updateProgress(String progress) {
|
||||
if (!newDownloadAction.equals(progress)) {
|
||||
if (DEBUG) Log.d("BR", "up " + progress);
|
||||
Intent intent = new Intent(BInstallerActivity.DOWNLOAD_ACTION);
|
||||
intent.putExtra("txt", progress);
|
||||
intent.putExtra("ready", bIsDownloading);
|
||||
sendBroadcast(intent);
|
||||
;
|
||||
newDownloadAction = progress;
|
||||
mNotificationHelper.progressUpdate(newDownloadAction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String download(int counter, int size, String surl) {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
HttpURLConnection connection = null;
|
||||
File fname = null;
|
||||
File tmp_file = null;
|
||||
try {
|
||||
try {
|
||||
TrafficStats.setThreadStatsTag(1);
|
||||
|
||||
int slidx = surl.lastIndexOf("segments4/");
|
||||
String name = surl.substring(slidx + 10);
|
||||
String surlBase = surl.substring(0, slidx + 10);
|
||||
fname = new File(baseDir, "segments4/" + name);
|
||||
|
||||
boolean delta = true;
|
||||
|
||||
// if (!targetFile.getParentFile().exists()) targetFile.getParentFile().mkdirs();
|
||||
if (fname.exists()) {
|
||||
updateProgress("Calculating local checksum..");
|
||||
|
||||
// first check for a delta file
|
||||
String md5 = Rd5DiffManager.getMD5(fname);
|
||||
String surlDelta = surlBase + "diff/" + name.replace(".rd5", "/" + md5 + ".df5");
|
||||
|
||||
URL urlDelta = new URL(surlDelta);
|
||||
|
||||
connection = (HttpURLConnection) urlDelta.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.connect();
|
||||
|
||||
// 404 kind of expected here, means there's no delta file
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
connection = null;
|
||||
} else {
|
||||
updateProgress("Connecting.." + surlDelta);
|
||||
}
|
||||
}
|
||||
|
||||
if (connection == null) {
|
||||
updateProgress("Connecting.." + name);
|
||||
|
||||
delta = false;
|
||||
URL url = new URL(surl);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.connect();
|
||||
}
|
||||
|
||||
updateProgress("Connecting.." + counter + "/" + size);
|
||||
|
||||
// expect HTTP 200 OK, so we don't mistakenly save error report
|
||||
// instead of the file
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
return "Server returned HTTP " + connection.getResponseCode()
|
||||
+ " " + connection.getResponseMessage();
|
||||
}
|
||||
|
||||
// this will be useful to display download percentage
|
||||
// might be -1: server did not report the length
|
||||
int fileLength = connection.getContentLength();
|
||||
long currentDownloadSize = fileLength;
|
||||
if (availableSize >= 0 && fileLength > availableSize) return "not enough space on sd-card";
|
||||
|
||||
currentDownloadOperation = delta ? "Updating" : "Loading";
|
||||
updateProgress(currentDownloadOperation);
|
||||
|
||||
// download the file
|
||||
input = connection.getInputStream();
|
||||
|
||||
tmp_file = new File(fname.getAbsolutePath() + (delta ? "_diff" : "_tmp"));
|
||||
output = new FileOutputStream(tmp_file);
|
||||
|
||||
byte[] data = new byte[4096];
|
||||
long total = 0;
|
||||
long t0 = System.currentTimeMillis();
|
||||
int count;
|
||||
while ((count = input.read(data)) != -1) {
|
||||
if (isCanceled()) {
|
||||
return "Download canceled!";
|
||||
}
|
||||
total += count;
|
||||
// publishing the progress....
|
||||
if (fileLength > 0) // only if total length is known
|
||||
{
|
||||
int pct = (int) (total * 100 / fileLength);
|
||||
updateProgress("Progress " + counter + "/" + size + " .. " + pct + "%");
|
||||
} else {
|
||||
updateProgress("Progress (unnown size)");
|
||||
}
|
||||
|
||||
output.write(data, 0, count);
|
||||
|
||||
// enforce < 2 Mbit/s
|
||||
long dt = t0 + total / 524 - System.currentTimeMillis();
|
||||
if (dt > 0) {
|
||||
try {
|
||||
Thread.sleep(dt);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
output.close();
|
||||
output = null;
|
||||
|
||||
if (delta) {
|
||||
updateProgress("Applying delta..");
|
||||
File diffFile = tmp_file;
|
||||
tmp_file = new File(fname + "_tmp");
|
||||
Rd5DiffTool.recoverFromDelta(fname, diffFile, tmp_file, this);
|
||||
diffFile.delete();
|
||||
}
|
||||
if (isCanceled()) {
|
||||
return "Canceled!";
|
||||
}
|
||||
if (tmp_file != null) {
|
||||
updateProgress("Verifying integrity..");
|
||||
String check_result = PhysicalFile.checkFileIntegrity(tmp_file);
|
||||
if (check_result != null) {
|
||||
if (check_result.startsWith("version old lookups.dat")) {
|
||||
|
||||
}
|
||||
return check_result;
|
||||
}
|
||||
if (fname.exists()) fname.delete();
|
||||
if (!tmp_file.renameTo(fname)) {
|
||||
return "Could not rename to " + fname.getAbsolutePath();
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace(); ;
|
||||
return e.toString();
|
||||
} finally {
|
||||
try {
|
||||
if (output != null)
|
||||
output.close();
|
||||
if (input != null)
|
||||
input.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
}
|
||||
} finally {
|
||||
if (tmp_file != null) tmp_file.delete(); // just to be sure
|
||||
}
|
||||
}
|
||||
|
||||
private String checkScripts() {
|
||||
|
||||
String[] sa = mServerConfig.getLookups();
|
||||
for (String f : sa) {
|
||||
if (f.length() > 0) {
|
||||
File file = new File(baseDir + "profiles2", f);
|
||||
checkOrDownloadLookup(f, file);
|
||||
}
|
||||
}
|
||||
|
||||
sa = mServerConfig.getProfiles();
|
||||
for (String f : sa) {
|
||||
if (f.length() > 0) {
|
||||
File file = new File(baseDir + "profiles2", f);
|
||||
if (file.exists()) {
|
||||
String result = checkOrDownloadScript(f, file);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String checkOrDownloadLookup(String fileName, File f) {
|
||||
String url = mServerConfig.getLookupUrl() + fileName;
|
||||
return downloadScript(url, f);
|
||||
}
|
||||
|
||||
private String checkOrDownloadScript(String fileName, File f) {
|
||||
String url = mServerConfig.getProfilesUrl() + fileName;
|
||||
return downloadScript(url, f);
|
||||
}
|
||||
|
||||
private String downloadScript(String surl, File f) {
|
||||
long size = 0L;
|
||||
if (f.exists()) {
|
||||
size = f.length();
|
||||
}
|
||||
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
HttpURLConnection connection = null;
|
||||
File tmp_file = null;
|
||||
File targetFile = f;
|
||||
|
||||
try {
|
||||
try {
|
||||
TrafficStats.setThreadStatsTag(1);
|
||||
|
||||
URL url = new URL(surl);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.connect();
|
||||
|
||||
// expect HTTP 200 OK, so we don't mistakenly save error report
|
||||
// instead of the file
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
return "Server returned HTTP " + connection.getResponseCode()
|
||||
+ " " + connection.getResponseMessage() + " " + f.getName();
|
||||
}
|
||||
|
||||
|
||||
// this will be useful to display download percentage
|
||||
// might be -1: server did not report the length
|
||||
long fileLength = (long) connection.getContentLength();
|
||||
if (DEBUG) Log.d("BR", "file size " + size + " == " + fileLength + " " + f.getName());
|
||||
if (fileLength != size) {
|
||||
long currentDownloadSize = fileLength;
|
||||
if (availableSize >= 0 && fileLength > availableSize)
|
||||
return "not enough space on sd-card";
|
||||
|
||||
currentDownloadOperation = "Updating";
|
||||
|
||||
// download the file
|
||||
input = connection.getInputStream();
|
||||
|
||||
tmp_file = new File(f.getAbsolutePath() + "_tmp");
|
||||
output = new FileOutputStream(tmp_file);
|
||||
|
||||
byte data[] = new byte[4096];
|
||||
long total = 0;
|
||||
long t0 = System.currentTimeMillis();
|
||||
int count;
|
||||
while ((count = input.read(data)) != -1) {
|
||||
if (isCanceled()) {
|
||||
return "Download canceled!";
|
||||
}
|
||||
total += count;
|
||||
// publishing the progress....
|
||||
if (fileLength > 0) // only if total length is known
|
||||
{
|
||||
int pct = (int) (total * 100 / fileLength);
|
||||
updateProgress("Progress " + pct + "%");
|
||||
} else {
|
||||
updateProgress("Progress (unnown size)");
|
||||
}
|
||||
|
||||
output.write(data, 0, count);
|
||||
|
||||
// enforce < 2 Mbit/s
|
||||
long dt = t0 + total / 524 - System.currentTimeMillis();
|
||||
if (dt > 0) {
|
||||
try {
|
||||
Thread.sleep(dt);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
output.close();
|
||||
output = null;
|
||||
}
|
||||
|
||||
if (isCanceled()) {
|
||||
return "Canceled!";
|
||||
}
|
||||
if (tmp_file != null) {
|
||||
f.delete();
|
||||
|
||||
if (!tmp_file.renameTo(f)) {
|
||||
return "Could not rename to " + f.getName();
|
||||
}
|
||||
if (DEBUG) Log.d("BR", "update " + f.getName());
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return e.toString();
|
||||
} finally {
|
||||
try {
|
||||
if (output != null)
|
||||
output.close();
|
||||
if (input != null)
|
||||
input.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
|
||||
}
|
||||
} finally {
|
||||
if (tmp_file != null) tmp_file.delete(); // just to be sure
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean isCanceled() {
|
||||
return BInstallerView.downloadCanceled;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ForegroundInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Random;
|
||||
|
||||
import btools.mapaccess.PhysicalFile;
|
||||
import btools.mapaccess.Rd5DiffManager;
|
||||
import btools.mapaccess.Rd5DiffTool;
|
||||
import btools.util.ProgressListener;
|
||||
|
||||
public class DownloadWorker extends Worker {
|
||||
public static final String KEY_INPUT_SEGMENT_NAMES = "SEGMENT_NAMES";
|
||||
public static final String PROGRESS_SEGMENT_NAME = "PROGRESS_SEGMENT_NAME";
|
||||
public static final String PROGRESS_SEGMENT_PERCENT = "PROGRESS_SEGMENT_PERCENT";
|
||||
|
||||
private final static boolean DEBUG = false;
|
||||
private static final int NOTIFICATION_ID = new Random().nextInt();
|
||||
private static final String PROFILES_DIR = "profiles2/";
|
||||
private static final String SEGMENTS_DIR = "segments4/";
|
||||
private static final String SEGMENT_DIFF_SUFFIX = ".df5";
|
||||
private static final String SEGMENT_SUFFIX = ".rd5";
|
||||
private static final String LOG_TAG = "DownloadWorker";
|
||||
|
||||
private final NotificationManager notificationManager;
|
||||
private final ServerConfig mServerConfig;
|
||||
private final File baseDir;
|
||||
private final ProgressListener diffProgressListener;
|
||||
private final DownloadProgressListener downloadProgressListener;
|
||||
private final Data.Builder progressBuilder = new Data.Builder();
|
||||
private final NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
public DownloadWorker(
|
||||
@NonNull Context context,
|
||||
@NonNull WorkerParameters parameters) {
|
||||
super(context, parameters);
|
||||
notificationManager = (NotificationManager)
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mServerConfig = new ServerConfig(context);
|
||||
baseDir = new File(ConfigHelper.getBaseDir(context), "brouter");
|
||||
|
||||
notificationBuilder = createNotificationBuilder();
|
||||
|
||||
downloadProgressListener = new DownloadProgressListener() {
|
||||
private String currentDownloadName;
|
||||
private DownloadType currentDownloadType;
|
||||
private int lastProgressPercent;
|
||||
|
||||
@Override
|
||||
public void onDownloadStart(String downloadName, DownloadType downloadType) {
|
||||
currentDownloadName = downloadName;
|
||||
currentDownloadType = downloadType;
|
||||
if (downloadType == DownloadType.SEGMENT) {
|
||||
progressBuilder.putString(PROGRESS_SEGMENT_NAME, downloadName);
|
||||
notificationBuilder.setContentText(downloadName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadInfo(String info) {
|
||||
notificationBuilder.setContentText(currentDownloadName + ": " + info);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadProgress(int max, int progress) {
|
||||
int progressPercent = (int) (progress * 100L / max);
|
||||
|
||||
// Only report segments and update if it changed to avoid hammering NotificationManager
|
||||
if (currentDownloadType != DownloadType.SEGMENT || progressPercent == lastProgressPercent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (max > 0) {
|
||||
notificationBuilder.setProgress(max, progress, false);
|
||||
progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, progressPercent);
|
||||
} else {
|
||||
notificationBuilder.setProgress(0, 0, true);
|
||||
progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1);
|
||||
}
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
setProgressAsync(progressBuilder.build());
|
||||
|
||||
lastProgressPercent = progressPercent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadFinished() {
|
||||
}
|
||||
};
|
||||
|
||||
diffProgressListener = new ProgressListener() {
|
||||
@Override
|
||||
public void updateProgress(String task, int progress) {
|
||||
downloadProgressListener.onDownloadInfo(task);
|
||||
downloadProgressListener.onDownloadProgress(100, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanceled() {
|
||||
return isStopped();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
Data inputData = getInputData();
|
||||
String[] segmentNames = inputData.getStringArray(KEY_INPUT_SEGMENT_NAMES);
|
||||
if (segmentNames == null) {
|
||||
if (DEBUG) Log.d(LOG_TAG, "Failure: no segmentNames");
|
||||
return Result.failure();
|
||||
}
|
||||
notificationBuilder.setContentText("Starting Download");
|
||||
// Mark the Worker as important
|
||||
setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, notificationBuilder.build()));
|
||||
try {
|
||||
if (DEBUG) Log.d(LOG_TAG, "Download lookup & profiles");
|
||||
downloadLookupAndProfiles();
|
||||
|
||||
for (String segmentName : segmentNames) {
|
||||
downloadProgressListener.onDownloadStart(segmentName, DownloadType.SEGMENT);
|
||||
if (DEBUG) Log.d(LOG_TAG, "Download segment " + segmentName);
|
||||
downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(LOG_TAG, e);
|
||||
return Result.failure();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(LOG_TAG, e);
|
||||
return Result.failure();
|
||||
}
|
||||
if (DEBUG) Log.d(LOG_TAG, "doWork finished");
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private void downloadLookupAndProfiles() throws IOException, InterruptedException {
|
||||
String[] lookups = mServerConfig.getLookups();
|
||||
for (String fileName : lookups) {
|
||||
if (fileName.length() > 0) {
|
||||
File lookupFile = new File(baseDir, PROFILES_DIR + fileName);
|
||||
String lookupLocation = mServerConfig.getLookupUrl() + fileName;
|
||||
URL lookupUrl = new URL(lookupLocation);
|
||||
downloadProgressListener.onDownloadStart(fileName, DownloadType.LOOKUP);
|
||||
downloadFile(lookupUrl, lookupFile, false);
|
||||
downloadProgressListener.onDownloadFinished();
|
||||
}
|
||||
}
|
||||
|
||||
String[] profiles = mServerConfig.getProfiles();
|
||||
for (String fileName : profiles) {
|
||||
if (fileName.length() > 0) {
|
||||
File profileFile = new File(baseDir, PROFILES_DIR + fileName);
|
||||
if (profileFile.exists()) {
|
||||
String profileLocation = mServerConfig.getProfilesUrl() + fileName;
|
||||
URL profileUrl = new URL(profileLocation);
|
||||
downloadProgressListener.onDownloadStart(fileName, DownloadType.PROFILE);
|
||||
downloadFile(profileUrl, profileFile, false);
|
||||
downloadProgressListener.onDownloadFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadSegment(String segmentBaseUrl, String segmentName) throws IOException, InterruptedException {
|
||||
File segmentFile = new File(baseDir, SEGMENTS_DIR + segmentName);
|
||||
File segmentFileTemp = new File(segmentFile.getAbsolutePath() + "_tmp");
|
||||
try {
|
||||
if (segmentFile.exists()) {
|
||||
if (DEBUG) Log.d(LOG_TAG, "Calculating local checksum");
|
||||
String md5 = Rd5DiffManager.getMD5(segmentFile);
|
||||
String segmentDeltaLocation = segmentBaseUrl + "diff/" + segmentName.replace(SEGMENT_SUFFIX, "/" + md5 + SEGMENT_DIFF_SUFFIX);
|
||||
URL segmentDeltaUrl = new URL(segmentDeltaLocation);
|
||||
if (httpFileExists(segmentDeltaUrl)) {
|
||||
File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff");
|
||||
try {
|
||||
downloadFile(segmentDeltaUrl, segmentDeltaFile, true);
|
||||
if (DEBUG) Log.d(LOG_TAG, "Applying delta");
|
||||
Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, diffProgressListener);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to download & apply delta update", e);
|
||||
} finally {
|
||||
segmentDeltaFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!segmentFileTemp.exists()) {
|
||||
URL segmentUrl = new URL(segmentBaseUrl + segmentName);
|
||||
downloadFile(segmentUrl, segmentFileTemp, true);
|
||||
}
|
||||
|
||||
PhysicalFile.checkFileIntegrity(segmentFileTemp);
|
||||
if (segmentFile.exists()) {
|
||||
if (!segmentFile.delete()) {
|
||||
throw new IOException("Failed to delete existing " + segmentFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (!segmentFileTemp.renameTo(segmentFile)) {
|
||||
throw new IOException("Failed to write " + segmentFile.getAbsolutePath());
|
||||
}
|
||||
} finally {
|
||||
segmentFileTemp.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean httpFileExists(URL downloadUrl) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setRequestMethod("HEAD");
|
||||
connection.connect();
|
||||
|
||||
return connection.getResponseCode() == HttpURLConnection.HTTP_OK;
|
||||
}
|
||||
|
||||
private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException {
|
||||
HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IOException("HTTP Request failed");
|
||||
}
|
||||
int fileLength = connection.getContentLength();
|
||||
try (
|
||||
InputStream input = connection.getInputStream();
|
||||
OutputStream output = new FileOutputStream(outputFile)
|
||||
) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int total = 0;
|
||||
long t0 = System.currentTimeMillis();
|
||||
int count;
|
||||
while ((count = input.read(buffer)) != -1) {
|
||||
if (isStopped()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
total += count;
|
||||
output.write(buffer, 0, count);
|
||||
|
||||
downloadProgressListener.onDownloadProgress(fileLength, total);
|
||||
|
||||
if (limitDownloadSpeed) {
|
||||
// enforce < 16 Mbit/s
|
||||
long dt = t0 + total / 2096 - System.currentTimeMillis();
|
||||
if (dt > 0) {
|
||||
Thread.sleep(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private NotificationCompat.Builder createNotificationBuilder() {
|
||||
Context context = getApplicationContext();
|
||||
String id = context.getString(R.string.notification_channel_id);
|
||||
String title = context.getString(R.string.notification_title);
|
||||
String cancel = context.getString(R.string.cancel_download);
|
||||
// This PendingIntent can be used to cancel the worker
|
||||
PendingIntent intent = WorkManager.getInstance(context)
|
||||
.createCancelPendingIntent(getId());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createChannel();
|
||||
}
|
||||
|
||||
return new NotificationCompat.Builder(context, id)
|
||||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setOngoing(true)
|
||||
// Add the cancel action to the notification which can
|
||||
// be used to cancel the worker
|
||||
.addAction(android.R.drawable.ic_delete, cancel, intent);
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private void createChannel() {
|
||||
CharSequence name = getApplicationContext().getString(R.string.channel_name);
|
||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
NotificationChannel channel = new NotificationChannel(getApplicationContext().getString(R.string.notification_channel_id), name, importance);
|
||||
// Register the channel with the system; you can't change the importance
|
||||
// or other notification behaviors after this
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
enum DownloadType {
|
||||
LOOKUP,
|
||||
PROFILE,
|
||||
SEGMENT
|
||||
}
|
||||
|
||||
interface DownloadProgressListener {
|
||||
void onDownloadStart(String downloadName, DownloadType downloadType);
|
||||
|
||||
void onDownloadInfo(String info);
|
||||
|
||||
void onDownloadProgress(int max, int progress);
|
||||
|
||||
void onDownloadFinished();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public class NotificationHelper {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static String BRouterNotificationChannel1 = "brouter_channel_01";
|
||||
|
||||
private Context mContext;
|
||||
private int NOTIFICATION_ID = 111;
|
||||
private Notification mNotification;
|
||||
private NotificationManager mNotificationManager;
|
||||
private PendingIntent mContentIntent;
|
||||
private CharSequence mContentTitle;
|
||||
|
||||
public NotificationHelper(Context context) {
|
||||
if (DEBUG) Log.d("NH", "init ");
|
||||
mContext = context;
|
||||
createNotificationChannels();
|
||||
}
|
||||
|
||||
public void startNotification(Service service) {
|
||||
if (DEBUG) Log.d("NH", "startNotification ");
|
||||
|
||||
mNotification = createNotification("BRouter Download", "Download some files");
|
||||
|
||||
if (service != null) service.startForeground(NOTIFICATION_ID, mNotification);
|
||||
|
||||
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
|
||||
|
||||
}
|
||||
|
||||
public void progressUpdate(String text) {
|
||||
mNotification = createNotification("BRouter Download", text);
|
||||
mNotification.flags = Notification.FLAG_NO_CLEAR |
|
||||
Notification.FLAG_ONGOING_EVENT;
|
||||
|
||||
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
|
||||
}
|
||||
|
||||
|
||||
public Notification createNotification(String title, String desc) {
|
||||
|
||||
Intent resultIntent = new Intent(mContext, BInstallerActivity.class);
|
||||
|
||||
Intent notificationIntent = new Intent();
|
||||
mContentIntent = PendingIntent.getActivity(mContext, 0, resultIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mNotificationManager = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
|
||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, BRouterNotificationChannel1);
|
||||
builder.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentTitle(title)
|
||||
.setContentText(desc)
|
||||
.setTicker(desc)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentIntent(mContentIntent);
|
||||
|
||||
return builder.build();
|
||||
|
||||
} else {
|
||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
|
||||
builder.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentTitle(title)
|
||||
.setContentText(desc)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentIntent(mContentIntent);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* create notification channels
|
||||
*/
|
||||
public void createNotificationChannels() {
|
||||
if (DEBUG) Log.d("NH", "createNotificationChannels ");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
NotificationManager sNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
// Sound channel
|
||||
CharSequence name = "BRouter Download";
|
||||
// The user-visible description of the channel.
|
||||
String description = "BRouter Download Channel"; //getString(R.string.channel_description);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(BRouterNotificationChannel1, name, NotificationManager.IMPORTANCE_LOW);
|
||||
channel.setDescription(description);
|
||||
AudioAttributes att = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_UNKNOWN)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
|
||||
.build();
|
||||
channel.setSound(null, null);
|
||||
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
|
||||
sNotificationManager.createNotificationChannel(channel);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void stopNotification() {
|
||||
if (DEBUG) Log.d("NH", "stopNotification ");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
mNotificationManager.deleteNotificationChannel(BRouterNotificationChannel1);
|
||||
}
|
||||
mNotificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/view_segments"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<btools.routingapp.BInstallerView
|
||||
android:id="@+id/BInstallerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDownload"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/action_select"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewSegmentSummary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="@android:color/primary_text_light"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressDownload"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -15,7 +15,22 @@
|
|||
-->
|
||||
|
||||
<resources>
|
||||
<plurals name="numberOfSegments">
|
||||
<item quantity="one">%d segment</item>
|
||||
<item quantity="other">%d segments</item>
|
||||
</plurals>
|
||||
<string name="app_name">BRouter</string>
|
||||
<string name="cancel_download">Cancel Download</string>
|
||||
<string name="import_profile">Import Profile</string>
|
||||
<string name="profile_filename_example">filename.brf</string>
|
||||
<string name="download_info_start">Starting download…</string>
|
||||
<string name="download_info_cancel">Cancelling…</string>
|
||||
<string name="action_download">Download %s</string>
|
||||
<string name="action_delete">Delete %s</string>
|
||||
<string name="action_update">Update %s</string>
|
||||
<string name="action_select">Select segments</string>
|
||||
<string name="summary_segments">Size=%s\nFree=%s</string>
|
||||
<string name="notification_channel_id">brouter_download</string>
|
||||
<string name="notification_title">Download Segments</string>
|
||||
<string name="channel_name">Downloads</string>
|
||||
</resources>
|
||||
|
|
|
|||
10
brouter-routing-app/src/main/res/values/styles.xml
Normal file
10
brouter-routing-app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.App" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="linearProgressIndicatorStyle">@style/Widget.App.LinearProgressIndicator</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.App.LinearProgressIndicator" parent="Widget.MaterialComponents.LinearProgressIndicator">
|
||||
<item name="trackThickness">20dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue