diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000..e984122eb --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,49 @@ +name: Android CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Create Google Services JSON File + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + run: rm -f app/google-services.json && (echo $GOOGLE_SERVICES_JSON | base64 -di > app/google-services.json) + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + + - name: Sign APK with keystore + uses: r0adkll/sign-android-release@v1 + id: sign_app + with: + releaseDirectory: app/build/outputs/apk/release + signingKeyBase64: ${{ secrets.KEY_STORE }} + alias: ${{ secrets.KEY_STORE_ALIAS }} + keyStorePassword: ${{ secrets.KEY_STORE_PASS }} + keyPassword: ${{ secrets.KEY_STORE_PASS }} + env: + BUILD_TOOLS_VERSION: "34.0.0" + + - name: Upload release APK + uses: actions/upload-artifact@v4 + with: + name: app-release.apk + path: ${{steps.sign_app.outputs.signedReleaseFile}} diff --git a/.github/workflows/weather.yml b/.github/workflows/weather.yml new file mode 100644 index 000000000..d8918e4b7 --- /dev/null +++ b/.github/workflows/weather.yml @@ -0,0 +1,38 @@ +name: Weather CI + + +on: + schedule: + - cron: '*/10 * * * *' + +jobs: + build: + + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v3 + with: + python-version: "3.12" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + sudo apt-get update + sudo apt-get install --fix-missing gdal-bin python3-gdal imagemagick python3-bs4 libwww-perl libxml-parser-perl + pip install regex urllib3 + - name: Build weather + run: | + cd extra/mamba && ./put_tenmin.sh + + - name: SSH to mamba + uses: appleboy/scp-action@v0.1.7 + with: + host: apps4av.org + username: apps4av + password: ${{ secrets.MAMBA_PASSWORD }} + port: 22 + strip_components: 2 + source: "extra/mamba/TFRs.zip,extra/mamba/weather.zip,extra/mamba/conus.zip" + target: /home/apps4av/mamba.dreamhosters.com/new diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bd9bd5a85..000000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: android -jdk: oraclejdk8 -sudo: false - -android: - components: - # use the latest revision of Android SDK Tools - - tools - - platform-tools - # The BuildTools version used by the project - - build-tools-25.0.0 - # The SDK version used to compile the project - - android-25 - # Additional components, otherwise bild fails to find license signature - - extra-google-google_play_services - - extra-google-m2repository - - extra-android-m2repository - -licenses: - - 'android-sdk-preview-license-.+' - - 'android-sdk-license-.+' - - 'google-gdk-license-.+' - -script: - bash gradlew test --continue --stacktrace \ No newline at end of file diff --git a/README.md b/README.md index bfc5c6c6f..00f73ddb3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ avare ===== +Note: A more modern multi platofrm version of Avare is available as AvareX on Android Play Store, Apple App Store, Windows App Store, and Linux Snapcraft Store. + Avare Aviation GPS for Android. Avare is pronounced "Ah-vAir" - like "aware" with a "v" and can be manually installed from our servers (see our website). Download from the Google Play Store: https://play.google.com/store/apps/details?id=com.ds.avare&hl=en diff --git a/app/build.gradle b/app/build.gradle index d41dcb194..84dcf1487 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' android { - compileSdkVersion 33 - buildToolsVersion '33.0.2' + compileSdk 35 + buildToolsVersion '34.0.0' defaultConfig { applicationId "com.ds.avare" - minSdkVersion 20 // android 5 is minimum - targetSdkVersion 33 + minSdkVersion 20 + targetSdk 35// android 5 is minimum } compileOptions { @@ -56,12 +56,12 @@ dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.2' implementation 'com.google.firebase:firebase-crashlytics:18.2.6' implementation 'com.github.mik3y:usb-serial-for-android:3.4.6' + implementation 'androidx.exifinterface:exifinterface:1.3.7' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:4.8.1' testImplementation 'org.powermock:powermock-module-junit4:1.7.0RC2' testImplementation 'org.powermock:powermock-api-mockito2:1.7.0RC2' testImplementation 'org.powermock:powermock-classloading-xstream:1.7.0RC2' testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.0RC2' - testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'org.json:json:20220924' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a83b9bbd9..f00a8f270 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,8 +11,8 @@ Redistribution and use in source and binary forms, with or without modification, * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> diff --git a/app/src/main/assets/help.html b/app/src/main/assets/help.html index c50da40aa..ada1c75a9 100644 --- a/app/src/main/assets/help.html +++ b/app/src/main/assets/help.html @@ -16,7 +16,7 @@ -

Avare Help

+

Avare Help

Donations
Apps4Av Inc. is a registered non-profit organization in the state of MA.
Avare is an open source project on GitHub. @@ -48,7 +48,6 @@
Thank you! -

Notice

ALL GPS applications on any handheld non-certified devices like smartphones and tablets are not approved by the FAA for IFR flights. None of the operating systems on such devices are tested according to rigorous FAA standards, hence any software running on them is unfit for use as a primary flight navigation tool. It is unwise to solely rely on any such device or any app running on one, @@ -60,95 +59,58 @@

Notice

CAUTION - Before flight the user must always ensure that Avare is updated to and thoroughly tested on the latest version, and ensure that all Avare databases and charts are kept current. If Avare and its databases and charts are not of the exact same version, the GPS position displayed may be inaccurate, because the FAA sometimes changes the format of their materials. Avare does not automatically fetch any databases and charts when they are expired, so it is the user's sole responsibility to update any expired charts and databases. To do so, ensure that your device has an internet connection and then just press the Map, Menu, Preferences, Download, and Update buttons in Avare.

Avare Releases

-

10.3.1

- -

10.3.0

- -

10.2.9

+

11.0.5

-

10.2.8

+

11.0.4

-

10.2.7

+

11.0.3

-

10.2.6

+

11.0.2

-

10.2.5

+

11.0.1

-

10.2.4

+

11.0.0

-

10.2.3

+

10.3.2

-

10.2.2

- -

10.2.1

+

10.3.1

-

10.2.0

+

10.3.0

-

10.1.9

+

10.2.9

-

10.1.8

+

10.2.8

-

10.1.7

+

10.2.7

Help Categories

Below is a list of your choices for Help with Avare (pronounced "Ah-vAir" - like "aware" with a "v"). Nearly all are available Offline (without internet access on your device), such as when you are flying or your device is in Airplane Mode.
@@ -172,14 +134,14 @@

Help Categories

Online* - Website & Forum

Online* - FAA Chart Maps
-

Online* - FAA TFR List +

Online* - FAA TFR List

Note: Under the Downloads list on the FAA website, select the chart type for which you'd like to see a map legend. For example, to see a map showing the area covered by each of the FAA Sectional charts, click on the "Sectional Raster Charts" link.


-

Avare Intro Videos

+< class="western" align="center">Avare Intro Videos> • Intro videos on YouTube at
      John Wiley's Avare Channel.
@@ -269,7 +231,7 @@

Quick Start - Intro     For convenience in selecting VORs, etc., airports are sorted toward the bottom since they can be quickly selected with a long-press in Map view. - +

Menu, the button on the lower left side of the Map screen, displays a list of additional options and configuration items for Avare. Use your Android Back key or gesture to return to the Map screen.
   ≡ The Preferences button for Avare is accessed via the Menu button, atop the following list of buttons for other features and options.
@@ -660,7 +622,7 @@

TPC and ONC charts

TPC and ONC charts are added for World coverage. These charts are expired and should not be used for navigation. To find the proper TPC chart for your area, see the TPC -Charts Grid while you are online. +Charts Grid while you are online.


  --- End of Avare offline Help file --- diff --git a/app/src/main/java/com/ds/avare/BaseActivity.java b/app/src/main/java/com/ds/avare/BaseActivity.java index d024338e8..f0efce8f6 100644 --- a/app/src/main/java/com/ds/avare/BaseActivity.java +++ b/app/src/main/java/com/ds/avare/BaseActivity.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.location.GpsStatus; import android.location.Location; +import android.os.Build; import android.os.Bundle; import android.view.Window; @@ -21,6 +22,12 @@ public class BaseActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { Helper.setTheme(this); + //apply theme style + + // apply this for android v35 or above, opt out of edge to edge enforcement + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + this.getTheme().applyStyle(R.style.OptOutEdgeToEdgeEnforcement, /* force */ false); + } super.onCreate(savedInstanceState); diff --git a/app/src/main/java/com/ds/avare/LocationActivity.java b/app/src/main/java/com/ds/avare/LocationActivity.java index a7ae4029e..417309728 100644 --- a/app/src/main/java/com/ds/avare/LocationActivity.java +++ b/app/src/main/java/com/ds/avare/LocationActivity.java @@ -268,6 +268,9 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { // start camera on volume down/up PackageManager packman = getPackageManager(); Intent intent; + if(!StorageService.getInstance().getPreferences().cameraButton()) { + return false; + } if (KeyEvent.KEYCODE_VOLUME_DOWN == event.getKeyCode()) { intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); } diff --git a/app/src/main/java/com/ds/avare/PlatesActivity.java b/app/src/main/java/com/ds/avare/PlatesActivity.java index aac9e24ac..11553bbc1 100644 --- a/app/src/main/java/com/ds/avare/PlatesActivity.java +++ b/app/src/main/java/com/ds/avare/PlatesActivity.java @@ -98,7 +98,6 @@ public class PlatesActivity extends BaseActivity implements Observer { private TankObserver mTankObserver; private TimerObserver mTimerObserver; - public static final String AD = "AIRPORT-DIAGRAM"; public static final String AREA = "AREA"; /* @@ -142,9 +141,6 @@ public static String getNameFromPath(String name) { * @return */ public float[] getMatrix(String name) { - if(name.equals(AD)) { - return(mMatrix); - } if(mService.getPlateDiagram() != null && mService.getPlateDiagram().getName() != null) { @@ -518,7 +514,7 @@ private void setPlateFromPos(int pos) { mPlatesView.setParams(null, true); float m[] = getMatrix(name); mService.setMatrix(null); // to small to show on map - if(name.startsWith(AD)) { + if(name.startsWith("APD")) { mPlatesView.setParams(m, true); } else if(name.startsWith(AREA)) { @@ -658,9 +654,8 @@ public boolean accept(File directory, String fileName) { String dplates[] = new File(mapFolder + "/plates/" + airport).list(filter); String aplates[] = new File(mapFolder + "/area/" + airport).list(filter); - String mins[] = mService.getDBResource().findMinimums(airport); - TreeMap plates = new TreeMap(new PlatesComparable()); + TreeMap plates = new TreeMap(); if (dplates != null) { for (String plate : dplates) { String tokens[] = plate.split(Preferences.IMAGE_EXTENSION); @@ -673,12 +668,6 @@ public boolean accept(File directory, String fileName) { plates.put(tokens[0], mapFolder + "/area/" + airport + "/" + tokens[0]); } } - if (mins != null) { - for (String plate : mins) { - String folder = plate.substring(0, 1) + "/"; - plates.put("Min. " + plate, mapFolder + "/minimums/" + folder + plate); - } - } if (plates.size() > 0) { mPlateFound = Arrays.asList(plates.values().toArray()).toArray(new String[plates.values().toArray().length]); mListPlates = new ArrayList(plates.keySet()); @@ -884,48 +873,6 @@ public void onResume() { } } - /** - * - * @author zkhan - * - */ - private class PlatesComparable implements Comparator{ - - @Override - public int compare(String o1, String o2) { - /* - * Airport diagram must be first - */ - String[] type = {AD, "AREA", "ILS-", "HI-ILS-", "LOC-", "HI-LOC-", "LDA-", "SDA-", "GPS-", "RNAV-GPS-", "RNAV-RNP-", "VOR-", "HI-VOR-", "TACAN-", "HI-TACAN-", "NDB-", "COPTER-", "CUSTOM-", "LAHSO", "HOT-SPOT", "Min."}; - - for(int i = 0; i < type.length; i++) { - if(o1.startsWith(type[i]) && (!o2.startsWith(type[i]))) { - return -1; - } - if(o2.startsWith(type[i]) && (!o1.startsWith(type[i]))) { - return 1; - } - } - - /* - * Continued must follow main - */ - String comp1 = o2.replace(Preferences.IMAGE_EXTENSION, ""); - if(o1.contains("-CONT.") && (!o2.contains("-CONT."))) { - if(o1.startsWith(comp1)) { - return 1; - } - } - String comp2 = o1.replace(Preferences.IMAGE_EXTENSION, ""); - if(o2.contains("-CONT.") && (!o1.contains("-CONT."))) { - if(o2.startsWith(comp2)) { - return -1; - } - } - - return o1.compareTo(o2); - } - } /** * @@ -944,7 +891,15 @@ public static boolean doesAirportHavePlates(String mapFolder, String id) { * @return */ public static boolean doesAirportHaveAirportDiagram(String mapFolder, String id) { - return new File(mapFolder + "/plates/" + id + "/" + AD + Preferences.IMAGE_EXTENSION).exists(); + String[] files = new File(mapFolder + "/plates/" + id + "/").list(); + if(null != files) { + for (String f : files) { + if (f.startsWith("APD")) { + return true; + } + } + } + return false; } diff --git a/app/src/main/java/com/ds/avare/PrefActivity.java b/app/src/main/java/com/ds/avare/PrefActivity.java index 2d5b1bda9..cf39e8595 100644 --- a/app/src/main/java/com/ds/avare/PrefActivity.java +++ b/app/src/main/java/com/ds/avare/PrefActivity.java @@ -20,6 +20,7 @@ import android.content.ServiceConnection; import android.location.GpsStatus; import android.location.Location; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceActivity; @@ -59,6 +60,12 @@ public void enabledCallback(boolean enabled) { public void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); Helper.setTheme(this); + + // apply this for android v35 or above, opt out of edge to edge enforcement + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + this.getTheme().applyStyle(R.style.OptOutEdgeToEdgeEnforcement, /* force */ false); + } + super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); diff --git a/app/src/main/java/com/ds/avare/RegisterActivity.java b/app/src/main/java/com/ds/avare/RegisterActivity.java index 9c20d86c2..101c58308 100644 --- a/app/src/main/java/com/ds/avare/RegisterActivity.java +++ b/app/src/main/java/com/ds/avare/RegisterActivity.java @@ -41,7 +41,7 @@ */ public class RegisterActivity extends BaseActivity { - private static final int MAX_ATTEMPTS = 5; + private static final int MAX_ATTEMPTS = 3; private static final int BACKOFF_MILLI_SECONDS = 2000; static AsyncTask mRegisterTask = null; @@ -183,7 +183,7 @@ protected Boolean doInBackground(Void... vals) { } backoff *= 2; } - return false; + return true; // pass anyways as people keep using old devices } @Override @@ -258,7 +258,7 @@ protected Boolean doInBackground(Void... vals) { backoff *= 2; } } - return false; + return true; // pass anyways as people keep using old devices } @Override diff --git a/app/src/main/java/com/ds/avare/adsb/gdl90/HeartbeatMessage.java b/app/src/main/java/com/ds/avare/adsb/gdl90/HeartbeatMessage.java index 8040a63ff..f01db02fb 100644 --- a/app/src/main/java/com/ds/avare/adsb/gdl90/HeartbeatMessage.java +++ b/app/src/main/java/com/ds/avare/adsb/gdl90/HeartbeatMessage.java @@ -22,9 +22,9 @@ public class HeartbeatMessage extends Message { int mMinute; int mSecond; - Boolean mGpsPositionValid; - Boolean mBatteryLow; - Boolean mDeviceRunning; + public Boolean mGpsPositionValid; + public Boolean mBatteryLow; + public Boolean mDeviceRunning; public HeartbeatMessage() { super(MessageType.HEARTBEAT); diff --git a/app/src/main/java/com/ds/avare/adsb/gdl90/OwnshipMessage.java b/app/src/main/java/com/ds/avare/adsb/gdl90/OwnshipMessage.java index ec0ce019b..96fdda7dd 100644 --- a/app/src/main/java/com/ds/avare/adsb/gdl90/OwnshipMessage.java +++ b/app/src/main/java/com/ds/avare/adsb/gdl90/OwnshipMessage.java @@ -27,8 +27,8 @@ public class OwnshipMessage extends Message { public boolean mIsAirborne; boolean mIsExtrapolated; int mTrackType; - int mNIC; - int mNACP; + public int mNIC; + public int mNACP; boolean mIsTrackHeadingValid; boolean mIsTrackHeadingTrueTrackAngle; boolean mIsTrackHeadingHeading; diff --git a/app/src/main/java/com/ds/avare/connections/BufferProcessor.java b/app/src/main/java/com/ds/avare/connections/BufferProcessor.java index 691514f2f..582e587dd 100644 --- a/app/src/main/java/com/ds/avare/connections/BufferProcessor.java +++ b/app/src/main/java/com/ds/avare/connections/BufferProcessor.java @@ -35,6 +35,7 @@ import com.ds.avare.adsb.gdl90.Constants; import com.ds.avare.adsb.gdl90.FisBuffer; import com.ds.avare.adsb.gdl90.FisGraphics; +import com.ds.avare.adsb.gdl90.HeartbeatMessage; import com.ds.avare.adsb.gdl90.Id11Product; import com.ds.avare.adsb.gdl90.Id12Product; import com.ds.avare.adsb.gdl90.Id13Product; @@ -156,8 +157,30 @@ else if(nmeaOwnship.addMessage(m)) { /* * Post on UI thread. */ - - if(m instanceof TrafficReportMessage) { + + if(m instanceof HeartbeatMessage) { + + /* + * Make a GPS heartbeat message from ADSB heartbeat message. + */ + JSONObject object = new JSONObject(); + HeartbeatMessage tm = (HeartbeatMessage)m; + try { + object.put("type", "heartbeat"); + object.put("timestamp", (long)tm.getTime()); + object.put("gpsvalid", (boolean)tm.mGpsPositionValid); + object.put("lowbattery", (boolean)tm.mBatteryLow); + object.put("running", (boolean)tm.mDeviceRunning); + } catch (JSONException e1) { + continue; + } + + objs.add(object.toString()); + + } + + + else if(m instanceof TrafficReportMessage) { /* * Make a GPS locaiton message from ADSB ownship message. @@ -523,6 +546,8 @@ else if(m instanceof OwnshipMessage) { object.put("time", (long)om.getTime()); object.put("altitude", (double) om.mAltitude); object.put("address", (int)om.mIcaoAddress); + object.put("nic", (int)om.mNIC); + object.put("nacp", (int)om.mNACP); } catch (JSONException e1) { continue; } diff --git a/app/src/main/java/com/ds/avare/connections/MsfsConnection.java b/app/src/main/java/com/ds/avare/connections/MsfsConnection.java index ba9205e26..d7d3b4807 100644 --- a/app/src/main/java/com/ds/avare/connections/MsfsConnection.java +++ b/app/src/main/java/com/ds/avare/connections/MsfsConnection.java @@ -160,6 +160,7 @@ public boolean connect(String to, boolean secure) { try { mSocket = new DatagramSocket(mPort); + mSocket.setReuseAddress(true); } catch(Exception e) { Logger.Logit("Failed! Connecting socket " + e.getMessage()); diff --git a/app/src/main/java/com/ds/avare/connections/WifiConnection.java b/app/src/main/java/com/ds/avare/connections/WifiConnection.java index f65453bb7..5604ec0dd 100644 --- a/app/src/main/java/com/ds/avare/connections/WifiConnection.java +++ b/app/src/main/java/com/ds/avare/connections/WifiConnection.java @@ -148,6 +148,7 @@ public boolean connect(String to, boolean secure) { try { mSocket = new DatagramSocket(mPort); + mSocket.setReuseAddress(true); } catch(Exception e) { Logger.Logit("Failed! Connecting socket " + e.getMessage()); diff --git a/app/src/main/java/com/ds/avare/connections/XplaneConnection.java b/app/src/main/java/com/ds/avare/connections/XplaneConnection.java index 698ab9e1d..a9f783667 100644 --- a/app/src/main/java/com/ds/avare/connections/XplaneConnection.java +++ b/app/src/main/java/com/ds/avare/connections/XplaneConnection.java @@ -147,6 +147,7 @@ public boolean connect(String to, boolean secure) { try { mSocket = new DatagramSocket(mPort); + mSocket.setReuseAddress(true); } catch(Exception e) { Logger.Logit("Failed! Connecting socket " + e.getMessage()); diff --git a/app/src/main/java/com/ds/avare/content/DataSource.java b/app/src/main/java/com/ds/avare/content/DataSource.java index e6e3a0ff5..80af3a0df 100644 --- a/app/src/main/java/com/ds/avare/content/DataSource.java +++ b/app/src/main/java/com/ds/avare/content/DataSource.java @@ -137,13 +137,6 @@ public StringPreference searchOneNoCache(String name) { return LocationContentProviderHelper.searchOne(mContext, name, true); } - public String[] findMinimums(String airportId) { - return LocationContentProviderHelper.findMinimums(mContext, airportId); - } - - public LinkedList findAFD(String airportId) { - return LocationContentProviderHelper.findAFD(mContext, airportId); - } public String findLonLat(String name, String type) { return LocationContentProviderHelper.findLonLat(mContext, name, type); diff --git a/app/src/main/java/com/ds/avare/message/NetworkHelper.java b/app/src/main/java/com/ds/avare/message/NetworkHelper.java index 33404e6f7..0c553d4b5 100644 --- a/app/src/main/java/com/ds/avare/message/NetworkHelper.java +++ b/app/src/main/java/com/ds/avare/message/NetworkHelper.java @@ -47,7 +47,7 @@ public class NetworkHelper { * This has to be provided by apps4av */ public static String getServer() { - return "https://apps4av.net/new/"; + return "https://www.apps4av.org/site/"; } /** diff --git a/app/src/main/java/com/ds/avare/place/Boundaries.java b/app/src/main/java/com/ds/avare/place/Boundaries.java index 5d6ac4fb4..6a5d0b174 100644 --- a/app/src/main/java/com/ds/avare/place/Boundaries.java +++ b/app/src/main/java/com/ds/avare/place/Boundaries.java @@ -1252,6 +1252,10 @@ public static ArrayList getChartTypes() { "13","OrlandoFLY","-82.0368","27.8287", "13","OrlandoFLY","-80.1406","27.8287", "13","OrlandoFLY","-80.1406","29.2311", + "13","PhoenixFLY","-112.8704026","34.0841324", + "13","PhoenixFLY","-112.855721","32.782496", + "13","PhoenixFLY","-111.1819443","32.7827780", + "13","PhoenixFLY","-111.1675846","34.0844160", "13","SaltLakeCityFLY","-112.909","41.4207", "13","SaltLakeCityFLY","-112.909","40.1204", "13","SaltLakeCityFLY","-111.016","40.1204", diff --git a/app/src/main/java/com/ds/avare/place/DatabaseDestination.java b/app/src/main/java/com/ds/avare/place/DatabaseDestination.java index 3be63a841..9a90b1226 100644 --- a/app/src/main/java/com/ds/avare/place/DatabaseDestination.java +++ b/app/src/main/java/com/ds/avare/place/DatabaseDestination.java @@ -122,35 +122,30 @@ protected Boolean doInBackground(Object... vals) { * Find Chart Supplement */ mAfdFound = null; - final LinkedList afdName = mDataSource.findAFD(mName); - if(afdName.size() > 0) { - FilenameFilter filter = new FilenameFilter() { - public boolean accept(File directory, String fileName) { - boolean match = false; - for(final String name : afdName) { - match |= fileName.matches(name + Preferences.IMAGE_EXTENSION) || - fileName.matches(name + "-[0-9]+" + Preferences.IMAGE_EXTENSION); - } - return match; - } - }; - String afd[] = null; - afd = new File(StorageService.getInstance().getPreferences().getServerDataFolder() + File.separator + "afd" + File.separator).list(filter); - if(null != afd) { - java.util.Arrays.sort(afd); - int len1 = afd.length; - String tmp1[] = new String[len1]; - for(int count = 0; count < len1; count++) { - /* - * Add Chart Supplement - */ - String tokens[] = afd[count].split(Preferences.IMAGE_EXTENSION); - tmp1[count] = StorageService.getInstance().getPreferences().getServerDataFolder() + File.separator + "afd" + File.separator + - tokens[0]; - } - if(len1 > 0) { - mAfdFound = tmp1; + String afd[] = null; + afd = new File(StorageService.getInstance().getPreferences().getServerDataFolder() + File.separator + "afd" + File.separator+ mName + File.separator).list(new FilenameFilter() { + @Override + public boolean accept(File file, String s) { + return !s.equals(".nomedia"); + } + }); + if(null != afd) { + java.util.Arrays.sort(afd); + int len1 = afd.length; + String tmp1[] = new String[len1]; + for(int count = 0; count < len1; count++) { + if(afd[count].equals(".nomedia")) { + continue; } + /* + * Add Chart Supplement + */ + String tokens[] = afd[count].split(Preferences.IMAGE_EXTENSION); + tmp1[count] = StorageService.getInstance().getPreferences().getServerDataFolder() + File.separator + "afd" + File.separator + mName + File.separator + + tokens[0]; + } + if(len1 > 0) { + mAfdFound = tmp1; } } } diff --git a/app/src/main/java/com/ds/avare/shapes/Tile.java b/app/src/main/java/com/ds/avare/shapes/Tile.java index 6dcd3ef09..4a4ba5708 100644 --- a/app/src/main/java/com/ds/avare/shapes/Tile.java +++ b/app/src/main/java/com/ds/avare/shapes/Tile.java @@ -332,10 +332,16 @@ else if(null == tile.getBitmap()) { */ tile.getTransform().setScale(scaleFactor, scaleFactor); + + int tileXIndex = tilen % tiles.getXTilesNum(); + int tileYIndex = tilen / tiles.getXTilesNum(); + int centerTileX = (int)(tiles.getXTilesNum() / 2); + int centerTileY = (int)(tiles.getYTilesNum() / 2); + tile.getTransform().postTranslate( ctx.view.getWidth() / 2.f + ( - BitmapHolder.WIDTH / 2.f - + ((tilen % tiles.getXTilesNum()) * BitmapHolder.WIDTH - BitmapHolder.WIDTH * (int)(tiles.getXTilesNum() / 2)) + + ((tileXIndex - centerTileX) * BitmapHolder.WIDTH) + ctx.pan.getMoveX() + ctx.pan.getTileMoveX() * BitmapHolder.WIDTH - (float)ctx.movement.getOffsetLongitude()) * scaleFactor, @@ -343,7 +349,7 @@ else if(null == tile.getBitmap()) { ctx.view.getHeight() / 2.f + ( - BitmapHolder.HEIGHT / 2.f + ctx.pan.getMoveY() - + ((tilen / tiles.getXTilesNum()) * BitmapHolder.HEIGHT - BitmapHolder.HEIGHT * (int)(tiles.getYTilesNum() / 2)) + + ((tileYIndex - centerTileY) * BitmapHolder.HEIGHT) + ctx.pan.getTileMoveY() * BitmapHolder.HEIGHT - (float)ctx.movement.getOffsetLatitude() ) * scaleFactor); diff --git a/app/src/main/java/com/ds/avare/storage/Preferences.java b/app/src/main/java/com/ds/avare/storage/Preferences.java index 94271030c..fa7bdcdd4 100644 --- a/app/src/main/java/com/ds/avare/storage/Preferences.java +++ b/app/src/main/java/com/ds/avare/storage/Preferences.java @@ -83,13 +83,16 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi /* * Max memory and max screen size it will support */ + public static final long MEM_512 = 512 * 1024 * 1024; public static final long MEM_256 = 256 * 1024 * 1024; public static final long MEM_192 = 192 * 1024 * 1024; public static final long MEM_128 = 128 * 1024 * 1024; public static final long MEM_64 = 64 * 1024 * 1024; public static final long MEM_32 = 32 * 1024 * 1024; - + public static final int MEM_512_X = 13; // For modern devices (512MB+ heap) + public static final int MEM_512_Y = 11; + public static final int MEM_512_OH = 13; public static final int MEM_192_X = 9; public static final int MEM_192_Y = 7; public static final int MEM_192_OH = 13; @@ -193,7 +196,7 @@ public String getRoot() { val = "0"; } if (val.equals("0")) { - return "http://www.apps4av.org/new/"; + return "http://www.apps4av.org/regions/"; } else if (val.equals("1")) { return "https://avare.bubble.org/"; } else if (val.equals("2")) { @@ -218,7 +221,11 @@ public static int[] getTilesNumber(Context ctx, boolean useScreenSize) { */ long mem = Runtime.getRuntime().maxMemory(); - if (mem >= MEM_192) { + if (mem >= MEM_512) { + ret[0] = MEM_512_X; + ret[1] = MEM_512_Y; + ret[2] = MEM_512_OH; + } else if (mem >= MEM_192) { ret[0] = MEM_192_X; ret[1] = MEM_192_Y; ret[2] = MEM_192_OH; @@ -248,8 +255,11 @@ public static int[] getTilesNumber(Context ctx, boolean useScreenSize) { defaultDisplay.getMetrics(displayMetrics); int width = displayMetrics.widthPixels; int height = displayMetrics.heightPixels; - int tilesx = (width / BitmapHolder.WIDTH) + 2; // add 1 for round up, and 1 for zoom - int tilesy = (height / BitmapHolder.HEIGHT) + 2; + // Account for minimum effective zoom (scaleFactor * macroFactor minimum is 0.5) + // At 0.5x zoom, tiles appear half size, so we need 2x as many to cover the screen + float minEffectiveZoom = 0.5f; + int tilesx = (int)Math.ceil((width / BitmapHolder.WIDTH) / minEffectiveZoom) + 2; // +2 for rounding and buffer + int tilesy = (int)Math.ceil((height / BitmapHolder.HEIGHT) / minEffectiveZoom) + 2; // odd tiles only if (tilesx % 2 == 0) { @@ -1073,6 +1083,10 @@ public boolean removeB1Map() { return mPref.getBoolean(mContext.getString(R.string.b1map), false); } + public boolean cameraButton() { + return mPref.getBoolean(mContext.getString(R.string.cameraButton), false); + } + public boolean removeB3Plate() { return mPref.getBoolean(mContext.getString(R.string.b3plate), false); } diff --git a/app/src/main/java/com/ds/avare/utils/PngCommentReader.java b/app/src/main/java/com/ds/avare/utils/PngCommentReader.java index 73670defe..57bc7a115 100644 --- a/app/src/main/java/com/ds/avare/utils/PngCommentReader.java +++ b/app/src/main/java/com/ds/avare/utils/PngCommentReader.java @@ -1,9 +1,9 @@ package com.ds.avare.utils; + +import androidx.exifinterface.media.ExifInterface; + import java.io.FileInputStream; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; /** * Created by zkhan on 3/14/17. @@ -13,65 +13,37 @@ public class PngCommentReader { public static float[] readPlate(String fileName) { - - FileChannel channel = null; - - try { // parsing a file causes unknown issues, so surround entire with exception catch - channel = new FileInputStream(fileName).getChannel(); - MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); - buffer.order(ByteOrder.BIG_ENDIAN); // BE for PNGs - - byte[] sign = new byte[8]; - buffer.get(sign); - if(sign[0] == (byte)0x89 && sign[1] == (byte)0x50 && sign[2] == (byte)0x4E && sign[3] == (byte)0x47 && sign[4] == (byte)0x0D && sign[5] == (byte)0x0A && sign[6] == (byte)0x1A && sign[7] == (byte)0x0A) { - // valid sign - int len = 0; - byte[] type = new byte[4]; - do { - len = buffer.getInt(); - buffer.get(type); - if('t' == type[0] && 'E' == type[1] && 'X' == type[2] && 't' == type[3]) { - byte[] data = new byte[len]; - buffer.get(data); - for(int count = 0; count < data.length; count++) { - if(0 == data[count]) { // remove nulls - data[count] = ' '; - } - } - String txt = new String(data); - if(txt.startsWith("Comment")) { - txt = txt.replace("Comment", ""); - String toks[] = txt.split("[|]"); - if(4 == toks.length) { - float matrix[] = new float[4]; - matrix[0] = (float)Double.parseDouble(toks[0]); - matrix[1] = (float)Double.parseDouble(toks[1]); - matrix[2] = (float)Double.parseDouble(toks[2]); - matrix[3] = (float)Double.parseDouble(toks[3]); - return matrix; - } - } - buffer.position(buffer.position() + 4); // 4 for CRC - } - else { - buffer.position(buffer.position() + len + 4); // 4 for CRC - } - } - while(len > 0); - - } - + ExifInterface exif; + try { + exif = new ExifInterface(new FileInputStream(fileName)); } catch (Exception e) { - + return null; } - try { - channel.close(); + String comment = exif.getAttribute("UserComment"); + if(comment == null) { + return null; } - catch (Exception e) { + String[] toks = comment.split("\\|"); + if(toks.length == 4) { + float[] matrix = new float[4]; + matrix[0] = (float)Double.parseDouble(toks[0]); + matrix[1] = (float)Double.parseDouble(toks[1]); + matrix[2] = (float)Double.parseDouble(toks[2]); + matrix[3] = (float)Double.parseDouble(toks[3]); + return matrix; + } + if(toks.length == 6) { + float[] matrix = new float[12]; + matrix[6] = (float)Double.parseDouble(toks[0]); + matrix[7] = (float)Double.parseDouble(toks[1]); + matrix[8] = (float)Double.parseDouble(toks[2]); + matrix[9] = (float)Double.parseDouble(toks[3]); + matrix[10] = (float)Double.parseDouble(toks[4]); + matrix[11] = (float)Double.parseDouble(toks[5]); + return matrix; } - return null; } } diff --git a/app/src/main/java/com/ds/avare/views/PlatesView.java b/app/src/main/java/com/ds/avare/views/PlatesView.java index 9b5bc75fa..f9130c651 100644 --- a/app/src/main/java/com/ds/avare/views/PlatesView.java +++ b/app/src/main/java/com/ds/avare/views/PlatesView.java @@ -244,7 +244,7 @@ public void onDraw(Canvas canvas) { */ - if (mShowingAD) { + if (mShowingAD && mMatrix.length == 12) { /* * Mike's matrix */ diff --git a/app/src/main/res/values-v35/values-v35.xml b/app/src/main/res/values-v35/values-v35.xml new file mode 100644 index 000000000..aa4c096cd --- /dev/null +++ b/app/src/main/res/values-v35/values-v35.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 8c1b9d9bd..84518f38f 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -45,59 +45,51 @@ Redistribution and use in source and binary forms, with or without modification, - Chart Supplement NE - Chart Supplement NC - Chart Supplement NW - Chart Supplement SE - Chart Supplement SC - Chart Supplement SW - Chart Supplement EC - Chart Supplement AK - Chart Supplement Pacific + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific - IFR Area ANC - IFR Area ATL - IFR Area DCA - IFR Area DEN - IFR Area DET - IFR Area DFW - IFR Area FAI - IFR Area GUA - IFR Area JAX - IFR Area JNU - IFR Area LAX - IFR Area MIA - IFR Area MKC - IFR Area MSP - IFR Area OME - IFR Area ORD - IFR Area PHX - IFR Area SFO - IFR Area STL - IFR Area VR + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific - IFR High Enroute NE - IFR High Enroute NC - IFR High Enroute NW - IFR High Enroute SE - IFR High Enroute SC - IFR High Enroute SW - IFR High Enroute AK + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific - IFR Low Enroute NE - IFR Low Enroute NC - IFR Low Enroute NW - IFR Low Enroute SE - IFR Low Enroute SC - IFR Low Enroute SW - IFR Low Enroute AK - IFR Low Enroute HI + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific @@ -125,179 +117,50 @@ Redistribution and use in source and binary forms, with or without modification, CJ-27 (Caribbean,PR,VI,DR) - Anchorage TAC - Atlanta TAC - Baltimore-Washington TAC - Boston TAC - Charlotte TAC - Chicago TAC - Cincinnati TAC - Cleveland TAC - Colorado Springs TAC - Dallas-FtWorth TAC - Denver TAC - Detroit TAC - Fairbanks TAC - Honolulu Inset - Houston TAC - Kansas City TAC - Las Vegas TAC - Los Angeles TAC - Memphis TAC - Miami TAC - Minneapolis-StPaul TAC - New Orleans TAC - New York TAC - Orlando TAC - Philadelphia TAC - Phoenix TAC - Pittsburgh TAC - Portland TAC - PuertoRico-VI TAC - Salt Lake City TAC - San Diego TAC - San Francisco TAC - Seattle TAC - St Louis TAC - Tampa TAC + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific - Atlanta Flyway - Baltimore-Washington Flyway - Charlotte Flyway - Chicago Flyway - Cincinnati Flyway - Dallas-FtWorth Flyway - Denver Flyway - Detroit Flyway - Houston Flyway - LasVegas Flyway - LosAngeles Flyway - Miami Flyway - NewOrleans Flyway - Orlando Flyway - SaltLakeCity Flyway - SanDiego Flyway - SanFrancisco Flyway - Seattle Flyway - StLouis Flyway - Tampa Flyway + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific - Albuquerque - Anchorage - Atlanta - Bethel - Billings - Brownsville - Cape Lisburne - Caribbean - 1 - Caribbean - 2 - Charlotte - Cheyenne - Chicago - Cincinnati - Cold Bay - Dallas-FtWorth - Dawson - Denver - Detroit - Dutch Harbor - El Paso - Fairbanks - Great Falls - Green Bay - Halifax - Hawaiian Islands - Houston - Jacksonville - Juneau - Kansas City - Ketchikan - Klamath Falls - Kodiak - Lake Huron - Las Vegas - Los Angeles - McGrath - Memphis - Miami - Montreal - New Orleans - New York - Nome - Omaha - Phoenix - Point Barrow - Salt Lake City - San Antonio - San Francisco - Seattle - Seward - St Louis - Twin Cities - Washington - Wichita + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific - Canada ADs - Minimums T/A - Alabama - Alaska - Arizona - Arkansas - California - Colorado - Connecticut - D.C. - Delaware - Florida - Georgia - Hawaii - Idaho - Illinois - Indiana - Iowa - Kansas - Kentucky - Louisiana - Maine - Maryland - Massachusetts - Michigan - Minnesota - Mississippi - Missouri - Montana - Nebraska - Nevada - New Hampshire - New Jersey - New Mexico - New York - North Carolina - North Dakota - Ohio - Oklahoma - Oregon - Pennsylvania - Puerto Rico - Rhode Island - South Carolina - South Dakota - Tennessee - Texas - Utah - Vermont - Virginia - Virgin Islands - Washington - West Virginia - Wisconsin - Wyoming - Others + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific VFR Area Canada @@ -464,19 +327,17 @@ Redistribution and use in source and binary forms, with or without modification, Canada Grid 340 Canada Grid 560 - + - Grand Canyon - Baltimore Heli - Boston Heli - Chicago Heli - Dallas-Ft-Worth Heli - Detroit Heli - Houston Heli - Los Angeles Heli - New York Heli - US Gulf Coast Heli - Washington Heli + Northeast + North Central + Northwest + Southeast + South Central + Southwest + East Central + Alaska + Pacific @@ -1512,59 +1373,51 @@ Redistribution and use in source and binary forms, with or without modification, - ELUS_NE - ELUS_NC - ELUS_NW - ELUS_SE - ELUS_SC - ELUS_SW - ELUS_AK - ELUS_HI + NE_ENR_L + NC_ENR_L + NW_ENR_L + SE_ENR_L + SC_ENR_L + SW_ENR_L + EC_ENR_L + AK_ENR_L + PAC_ENR_L - EHUS_NE - EHUS_NC - EHUS_NW - EHUS_SE - EHUS_SC - EHUS_SW - EHUS_AK + NE_ENR_H + NC_ENR_H + NW_ENR_H + SE_ENR_H + SC_ENR_H + SW_ENR_H + EC_ENR_H + AK_ENR_H + PAC_ENR_H - ENRA_ANC - ENRA_ATL - ENRA_DCA - ENRA_DEN - ENRA_DET - ENRA_DFW - ENRA_FAI - ENRA_GUA - ENRA_JAX - ENRA_JNU - ENRA_LAX - ENRA_MIA - ENRA_MKC - ENRA_MSP - ENRA_OME - ENRA_ORD - ENRA_PHX - ENRA_SFO - ENRA_STL - ENRA_VR + NE_ENR_A + NC_ENR_A + NW_ENR_A + SE_ENR_A + SC_ENR_A + SW_ENR_A + EC_ENR_A + AK_ENR_A + PAC_ENR_A - AFD_NE - AFD_NC - AFD_NW - AFD_SE - AFD_SC - AFD_SW - AFD_EC - AFD_AK - AFD_PAC + NE_CSUP + NC_CSUP + NW_CSUP + SE_CSUP + SC_CSUP + SW_CSUP + EC_CSUP + AK_CSUP + PAC_CSUP @@ -1592,156 +1445,39 @@ Redistribution and use in source and binary forms, with or without modification, CJ-27 - AnchorageTAC - AtlantaTAC - Baltimore-WashingtonTAC - BostonTAC - CharlotteTAC - ChicagoTAC - CincinnatiTAC - ClevelandTAC - ColoradoSpringsTAC - Dallas-FtWorthTAC - DenverTAC - DetroitTAC - FairbanksTAC - HonoluluInset - HoustonTAC - KansasCityTAC - LasVegasTAC - LosAngelesTAC - MemphisTAC - MiamiTAC - Minneapolis-StPaulTAC - NewOrleansTAC - NewYorkTAC - OrlandoTAC - PhiladelphiaTAC - PhoenixTAC - PittsburghTAC - PortlandTAC - PuertoRico-VITAC - SaltLakeCityTAC - SanDiegoTAC - SanFranciscoTAC - SeattleTAC - StLouisTAC - TampaTAC + NE_TAC + NC_TAC + NW_TAC + SE_TAC + SC_TAC + SW_TAC + EC_TAC + AK_TAC + PAC_TAC + - Albuquerque - Anchorage - Atlanta - Bethel - Billings - Brownsville - CapeLisburne - Caribbean1 - Caribbean2 - Charlotte - Cheyenne - Chicago - Cincinnati - ColdBay - Dallas-FtWorth - Dawson - Denver - Detroit - DutchHarbor - ElPaso - Fairbanks - GreatFalls - GreenBay - Halifax - HawaiianIslands - Houston - Jacksonville - Juneau - KansasCity - Ketchikan - KlamathFalls - Kodiak - LakeHuron - LasVegas - LosAngeles - McGrath - Memphis - Miami - Montreal - NewOrleans - NewYork - Nome - Omaha - Phoenix - PointBarrow - SaltLakeCity - SanAntonio - SanFrancisco - Seattle - Seward - StLouis - TwinCities - Washington - Wichita + NE_SEC + NC_SEC + NW_SEC + SE_SEC + SC_SEC + SW_SEC + EC_SEC + AK_SEC + PAC_SEC - CAN_ADS - alternates - AL_PLATES - AK_PLATES - AZ_PLATES - AR_PLATES - CA_PLATES - CO_PLATES - CT_PLATES - DC_PLATES - DE_PLATES - FL_PLATES - GA_PLATES - HI_PLATES - ID_PLATES - IL_PLATES - IN_PLATES - IA_PLATES - KS_PLATES - KY_PLATES - LA_PLATES - ME_PLATES - MD_PLATES - MA_PLATES - MI_PLATES - MN_PLATES - MS_PLATES - MO_PLATES - MT_PLATES - NE_PLATES - NV_PLATES - NH_PLATES - NJ_PLATES - NM_PLATES - NY_PLATES - NC_PLATES - ND_PLATES - OH_PLATES - OK_PLATES - OR_PLATES - PA_PLATES - PR_PLATES - RI_PLATES - SC_PLATES - SD_PLATES - TN_PLATES - TX_PLATES - UT_PLATES - VT_PLATES - VA_PLATES - VI_PLATES - WA_PLATES - WV_PLATES - WI_PLATES - WY_PLATES - XX_PLATES + NE_TPP + NC_TPP + NW_TPP + SE_TPP + SC_TPP + SW_TPP + EC_TPP + AK_TPP + PAC_TPP @@ -1910,42 +1646,29 @@ Redistribution and use in source and binary forms, with or without modification, CAN_340 CAN_560 - + - GrandCanyon - BaltimoreHeli - BostonHeli - ChicagoHeli - Dallas-FtWorthHeli - DetroitHeli - HoustonHeli - LosAngelesHeli - NewYorkHeli - USGulfCoastHeli - WashingtonHeli + NE_HEL + NC_HEL + NW_HEL + SE_HEL + SC_HEL + SW_HEL + EC_HEL + AK_HEL + PAC_HEL - AtlantaFLY - Baltimore-WashingtonFLY - CharlotteFLY - ChicagoFLY - CincinnatiFLY - Dallas-FtWorthFLY - DenverFLY - DetroitFLY - HoustonFLY - LasVegasFLY - LosAngelesFLY - MiamiFLY - NewOrleansFLY - OrlandoFLY - SaltLakeCityFLY - SanDiegoFLY - SanFranciscoFLY - SeattleFLY - StLouisFLY - TampaFLY + NE_FLY + NC_FLY + NW_FLY + SE_FLY + SC_FLY + SW_FLY + EC_FLY + AK_FLY + PAC_FLY @@ -2323,7 +2046,7 @@ Redistribution and use in source and binary forms, with or without modification, "Change the time on the fuel timer via Instrumentation->Fuel Time Interval" "If the app is not locking on to a GPS, move to a different area, and check GPS NOTAMs" "If pre-downloaded SUA or weather disappear in flight, check Weather->Data Expiry and Use ADSB Weather" - "If you're not ADS/B Out equipped, most traffic might be obscured from you" + "If you\'re not ADS/B Out equipped, most traffic might be obscured from you" "Avare is developed by a team volunteers and is free to use, but consider donating to support it" "If the app is running slow, uninstall some background running apps" "If the app is not locking on to a GPS, allow the app to access your location in Android Application Settings" @@ -2342,21 +2065,21 @@ Redistribution and use in source and binary forms, with or without modification, "Choose minimal length for fields in the Near tab via Display->Near Runway Minimum Length" "Set how long weather and TFRs are displayed for via Weather->Data Expiry" "To see maps from different areas, use simulation mode and enter a destination in that areas" - "Don't just keep Avare's data up-to-date, update Avare itself regularly via Google Play" + "Don\'t just keep Avare\'s data up-to-date, update Avare itself regularly via Google Play" "Your email address is a signature you leave for signing a liability release form" "To get support or report bugs, go to apps4av.com and look for the forum link" "If chart download fails after completing more than 50%, make sure your device has enough storage" - "Arrivals, departure procedures, alternate minimums and other info are included with a field's plates" + "Arrivals, departure procedures, alternate minimums and other info are included with a field\'s plates" "Practicing using Avare when flying as a passenger will make you more proficient using it" "Long press on an airport to show its METAR, TAF, COMM and other information" "Avare includes density altitude information for airports" "Avare works with most ADS/B receivers. Confirm compatibility before purchasing one" "When Avare encounters unfamliar fixes on "Create New Plan," it skips those fixes" "Every time you find a bug and report it, you help make Avare a better product" - "Avare's taxi diagrams are automatically geo-tagged, plates are manually tagged by volunteers" + "Avare\'s taxi diagrams are automatically geo-tagged, plates are manually tagged by volunteers" "Update your maps and plates well before a flight, the download process can be lengthy" "If you like this app, please leave a positive review on the play store, and/or donate" - "It's best to update maps and plates via Wi-Fi, since it takes a significant amount of data" + "It\'s best to update maps and plates via Wi-Fi, since it takes a significant amount of data" "You can save and restore your data to Google Drive from Preferences->Application State->Sync Data" "You can change the shown chart type in Map screen by long pressing on the Map tab" "You can modify the quantity of fields on the top of the display by adjusting the Avare font size scaling at Preferences->UI Configuration->Adjust Font Size" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 93c402bcb..f2cd5bbff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -640,5 +640,8 @@ Redistribution and use in source and binary forms, with or without modification, AircraftTailNumber Acft User defined aircraft. + CameraButton + Use the volume button to launch the camera app + Camera App diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index f55c6c882..aa080c8cb 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -515,6 +515,12 @@ authors: zkhan, jlmcgraw android:title="@string/Io" /> + + mockStaticLogger = mockStatic(Logger.class); MockedStatic mockStaticCrc = mockStatic(Crc.class)) { - mockStaticCrc.when(() -> Crc.checkCrc(any(), anyInt(), anyInt())).thenReturn(true); - JSONObject jsonResult; - // TODO: Validate lat, lon, etc. - // new parameters (isairborne, vspeed) test #1 - bp.put(testTrafficMessage, testTrafficMessage.length); - jsonResult = new JSONObject(bp.decode(mock(Preferences.class)).get(0)); - assertEquals("traffic", jsonResult.getString("type")); - assertEquals(true, jsonResult.getBoolean("isairborne")); - assertEquals(-64, jsonResult.getInt("vspeed")); - // new parameters (isairborne, vspeed) test #2 - bp.put(setAirborneFlagInCopy(testTrafficMessage, false), testTrafficMessage.length); - jsonResult = new JSONObject(bp.decode(mock(Preferences.class)).get(0)); - assertEquals(false, jsonResult.getBoolean("isairborne")); - } - } - - @Test - public void bufferProcessor_ownshipMessageDecode_properJsonMessageReturned() throws JSONException { - final BufferProcessor bp = new BufferProcessor(); - try (MockedStatic mockStaticLogger = mockStatic(Logger.class); MockedStatic mockStaticCrc = mockStatic(Crc.class)) { - mockStaticCrc.when(() -> Crc.checkCrc(any(), anyInt(), anyInt())).thenReturn(true); - JSONObject jsonResult; - // TODO: Validate lat, lon, etc. - // new parameters (isairborne, vspeed) test #1 - bp.put(testOwnshipMessage, testTrafficMessage.length); - jsonResult = new JSONObject(bp.decode(mock(Preferences.class)).get(0)); - assertEquals("ownship", jsonResult.getString("type")); - assertEquals(true, jsonResult.getBoolean("isairborne")); - assertEquals(192, jsonResult.getInt("vspeed")); - // new parameters (isairborne, vspeed) test #2 - bp.put(setAirborneFlagInCopy(testOwnshipMessage, false), testTrafficMessage.length); - jsonResult = new JSONObject(bp.decode(mock(Preferences.class)).get(0)); - assertEquals(false, jsonResult.getBoolean("isairborne")); - } - } - - private static final byte[] setAirborneFlagInCopy(final byte[] msg, boolean isAirborne) { - final byte[] newMsg = Arrays.copyOf(msg, msg.length); - newMsg[11+2] = (byte) (isAirborne ? (newMsg[11+2] | (1 << 3)) : (newMsg[11+2] & ~(1 << 3) )); - return newMsg; - } - -} diff --git a/app/src/test/java/com/ds/avare/adsb/AudibleTrafficAlertsTest.java b/app/src/test/java/com/ds/avare/adsb/AudibleTrafficAlertsTest.java deleted file mode 100644 index db154be14..000000000 --- a/app/src/test/java/com/ds/avare/adsb/AudibleTrafficAlertsTest.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.ds.avare.adsb; - - -import android.content.Context; -import android.location.Location; - -import org.junit.Assert; -import org.junit.Test; - -import static org.mockito.Mockito.*; - -import com.ds.avare.storage.Preferences; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class AudibleTrafficAlertsTest { - - @Test - public void angleFromCoordinate_straightUp() { - Assert.assertEquals(0, AudibleTrafficAlerts.angleFromCoordinate(0, 0, 90, 0), 0); - } - - @Test - public void angleFromCoordinate_east() { - Assert.assertEquals(90, AudibleTrafficAlerts.angleFromCoordinate(0, 0, 0, 45), 0); - } - - @Test - public void angleFromCoordinate_west() { - Assert.assertEquals(270, AudibleTrafficAlerts.angleFromCoordinate(0, 90, 0, 45), 0); - } - - @Test - public void angleFromCoordinate_diagonal() { - Assert.assertEquals(225, AudibleTrafficAlerts.angleFromCoordinate(43.5439, -96.730, 42.57, -98.0421), 0.5); - } - - @Test - public void nearestClockHourFromHeadingAndLocations_east() { - Assert.assertEquals(3, - AudibleTrafficAlerts.nearestClockHourFromHeadingAndLocations( - 0, 0, 0, 45, 0)); - } - - @Test - public void nearestClockHourFromHeadingAndLocations_nearEnoughToEastRounded() { - Assert.assertEquals(3, - AudibleTrafficAlerts.nearestClockHourFromHeadingAndLocations( - 0, 0, 0, 45, 5)); - } - - @Test - public void nearestClockHourFromHeadingAndLocations_straightBehindFromAnAngle() { - Assert.assertEquals(6, - AudibleTrafficAlerts.nearestClockHourFromHeadingAndLocations( - 43.5439, -96.730, 42.57, -98.0421, 44.999)); - } - - @Test - public void nearestClockHourFromHeadingAndLocations_oneMinuteToMidnightRounded() { - Assert.assertEquals(12, - AudibleTrafficAlerts.nearestClockHourFromHeadingAndLocations( - 43.5439, -96.730, 44.8402, -96.7621, 0)); - } - - @Test - public void nearestClockHourFromHeadingAndLocations_oneMinutePastMidnightRounded() { - Assert.assertEquals(12, - AudibleTrafficAlerts.nearestClockHourFromHeadingAndLocations( - 43.5439, -96.730, 48.034, -96.654, 0)); - } - - @Test - public void nearestClockHourFromHeadingAndLocations_nearlySouthAndIFacingSouth() { - Assert.assertEquals(12, - AudibleTrafficAlerts.nearestClockHourFromHeadingAndLocations( - 43.5439, -96.730, 39.5718, -96.735, 180)); - } - - @Test - public void closestApproachTime_eastWestHeadOn() { - final double lat1 = 45, lat2 = 45, lon1 = -95, lon2 = -94; - final int velocity1 = 60, velocity2 = 60; - final float heading1 = 270, heading2 = 90; - final double caTime = AudibleTrafficAlerts.closestApproachTime(lat1, lon1, lat2, lon2, heading1, heading2, velocity1, velocity2); - Assert.assertEquals("Closest approach seconds", .35, Math.abs(caTime), .1); - } - - @Test - public void closestApproachTime_northSouthHeadOn() { - final double lat1 = 45, lat2 = 46, lon1 = -95, lon2 = -95; - final int velocity1 = 60, velocity2 = 60; - final float heading1 = 180, heading2 = 0; - final double caTime = AudibleTrafficAlerts.closestApproachTime(lat1, lon1, lat2, lon2, heading1, heading2, velocity1, velocity2); - Assert.assertEquals("Closest approach seconds", .5, Math.abs(caTime), .1); - } - - @Test - public void locationAfterTime_northAt60ktsIsOneDegreeLat() { - final double lat = 41, lon = -95, time = 1 /* hour */; - final float velocity = 60; /* knots */ - final float heading = 0; /* North */ - double[] latLonOverTime = AudibleTrafficAlerts.locationAfterTime(lat, lon, heading, velocity, time,200, 20); - Assert.assertEquals("new lat", 42, latLonOverTime[0], 0.1); - Assert.assertEquals("new lon the same", lon, latLonOverTime[1], 0.1); - } - - @Test - public void locationAfterTime_eastAt60ktsNearEquatorIsAboutOneDegreeLon() { - final double lat = 10, lon = -95, time = 1 /* hour */; - final float velocity = 60; /* knots */ - final float heading = 90; /* East */ - double[] latLonOverTime = AudibleTrafficAlerts.locationAfterTime(lat, lon, heading, velocity, time, 200, 20); - Assert.assertEquals("new lat the same", lat, latLonOverTime[0], 0.0); - Assert.assertEquals("new lon", -94, latLonOverTime[1], 0.1); - } - - @Test - public void locationAfterTime_eastForAShortTimeSlowlyShowsSomeMovement() { - final double lat = 41.184505, lon = -95.948730, time = .008952 /* hour */; - final float velocity = 37; /* knots */ - final float heading = 90; /* East */ - double[] latLonOverTime = AudibleTrafficAlerts.locationAfterTime(lat, lon, heading, velocity, time, 200, 20); - Assert.assertEquals("new lat the same", lat, latLonOverTime[0], 0.0); - Assert.assertNotEquals("new lon", lon, latLonOverTime[1], 0.0000001); - } - - @Test - public void locationAfterTime_eastAt60ktsNearEquatorIsAboutOneDegreeLonHalfHour() { - final double lat = 10, lon = -95, time = .5 /* hour */; - final float velocity = 60; /* knots */ - final float heading = 90; /* East */ - double[] latLonOverTime = AudibleTrafficAlerts.locationAfterTime(lat, lon, heading, velocity, time, 20, 20); - Assert.assertEquals("new lat the same", lat, latLonOverTime[0], 0.0); - Assert.assertEquals("new lon", -94.5, latLonOverTime[1], 0.1); - } - - @Test - public void buildAlertSoundIdSequence_numericListPrefSet_closingEventLessThanHalfSecond_PicksFirstMedia() { - AudibleTrafficAlerts ata = getTestAudibleTrafficAlerts(10); - ata.distanceCalloutOption = AudibleTrafficAlerts.DistanceCalloutOption.DECIMAL; - Location mockLoc = getMockLocation(41.3,-95.4, 200.0f); - AudibleTrafficAlerts.Alert alert = new AudibleTrafficAlerts.Alert( - "abc123", 2, 75, - new AudibleTrafficAlerts.Alert.ClosingEvent(.15, 1.0, false), 99.9f, 10 - ); - List media = ata.buildAlertSoundIdSequence(alert, 1f); - final int firstMedia = ata.numberSoundIds[0]; - Assert.assertTrue("First media ["+firstMedia+"] used: "+media, media.contains(firstMedia)); - } - - @Test - public void handleAudibleAlerts_handleTrafficRunnableIsGarbageCollected() { - AudibleTrafficAlerts spyAta = spy(getTestAudibleTrafficAlerts(10)); - CapturingSingleThreadExecutor capEx = new CapturingSingleThreadExecutor(); - doReturn(capEx).when(spyAta).getTrafficAlertProducerExecutor(); - spyAta.handleAudibleAlerts( - getMockLocation(45, 46, 270), new LinkedList(), mock(Preferences.class), 2200, true, 20); - WeakReference runnableRef = new WeakReference<>(capEx.runnables.get(0)); - capEx.runnables.clear(); - forceGc(); - Assert.assertNull("Reference to runnable after GC", runnableRef.get()); - } - - @Test - public void handleAudibleAlerts_nullLocationDoesNotCauseRunnableExecutionOrError() { - AudibleTrafficAlerts spyAta = spy(getTestAudibleTrafficAlerts(10)); - CapturingSingleThreadExecutor capEx = new CapturingSingleThreadExecutor(); - doReturn(capEx).when(spyAta).getTrafficAlertProducerExecutor(); - LinkedList someTraffic = new LinkedList<>(); - Traffic t = new Traffic(); - t.mIsAirborne = true; - someTraffic.add(t); - spyAta.handleAudibleAlerts( - null, someTraffic, mock(Preferences.class), 2200, true, 20); - Assert.assertEquals("Executed runnables", 0, capEx.runnables.size()); - } - - @Test - public void addNumericalAlertAudioSequence_numericListPrefSet_largeNumberWithDecimal() { - final AudibleTrafficAlerts ata = getTestAudibleTrafficAlerts(10); - ata.numberFormatOption = AudibleTrafficAlerts.NumberFormatOption.INDIVIDUAL_DIGIT; - ata.distanceCalloutOption = AudibleTrafficAlerts.DistanceCalloutOption.DECIMAL; - final ArrayList soundIds = new ArrayList<>(); - ata.addNumericalAlertAudio(soundIds, 1049.99, true); - Assert.assertEquals("SoundIds from number", - Arrays.asList(ata.numberSoundIds[1], ata.numberSoundIds[0], ata.numberSoundIds[4], ata.numberSoundIds[9], ata.pointSoundId, ata.numberSoundIds[9]), - soundIds); - } - - private AudibleTrafficAlerts getTestAudibleTrafficAlerts(int secondsCount) { - final int[] seconds = new int[secondsCount]; - for (int i = 0; i < secondsCount; i++) - seconds[i] = 2000 + i; - return new AudibleTrafficAlerts(getMockSoundPlayer(), mock(Context.class)); - } - - - private Location getMockLocation(double latitude, double longitude, float bearing) { - final Location mockLoc = mock(Location.class); - when(mockLoc.getLatitude()).thenReturn(latitude); - when(mockLoc.getLongitude()).thenReturn(longitude); - when(mockLoc.getBearing()).thenReturn(bearing); - return mockLoc; - } - - private void forceGc() { - //allocate quite some memory to make sure that the GC runs - byte[] bigChunkOfMemory = new byte[4000000]; - System.gc(); - } - - private AudibleTrafficAlerts.SequentialSoundPoolPlayer getMockSoundPlayer() { - AudibleTrafficAlerts.SequentialSoundPoolPlayer sp = mock(AudibleTrafficAlerts.SequentialSoundPoolPlayer.class); - when(sp.load(any(), any())) - .thenAnswer(invocation -> { - int[] regurg = new int[invocation.getArguments().length-1]; - for (int i = 1; i < invocation.getArguments().length; i++) - regurg[i-1] = (int) invocation.getArgument(i); - return regurg; - }); - return sp; - } - - private static class CapturingSingleThreadExecutor implements Executor { - private ArrayList runnables = new ArrayList<>(); - private ExecutorService executor = Executors.newSingleThreadExecutor(); - @Override - public void execute(Runnable r) { - runnables.add(r); - executor.execute(r); - } - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ds/avare/test/DestinationGpsTest.java b/app/src/test/java/com/ds/avare/test/DestinationGpsTest.java deleted file mode 100644 index 48c4671e7..000000000 --- a/app/src/test/java/com/ds/avare/test/DestinationGpsTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.ds.avare.test; - -import com.ds.avare.AvareApplication; -import com.ds.avare.BuildConfig; -import com.ds.avare.StorageService; -import com.ds.avare.place.Destination; -import com.ds.avare.place.DestinationFactory; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import static junit.framework.TestCase.assertEquals; - -/** - * Created by pasniak on 4/24/2017. - */ - -@RunWith(RobolectricTestRunner.class) -@Config(application = AvareApplication.class) -@PowerMockIgnore({"org.mockito.", "org.robolectric."}) -@PrepareForTest({StorageService.class}) -public class DestinationGpsTest { - private StorageService mStorageService; - - @Before - public void setUp() { - mStorageService = Robolectric.setupService(StorageService.class); - } - @Test - public void Google() { - Destination d1 = DestinationFactory.build(mStorageService, "40.4747&-74.1844", Destination.GPS); - assertLatLon(40.4747, -74.1844, d1); - Destination d2 = DestinationFactory.build(mStorageService, "TEST@40.4747&-74.1844", Destination.GPS); - assertLatLon(40.4747, -74.1844, d2); - } - @Test - public void ICAOLong() { - Destination d1 = DestinationFactory.build(mStorageService, "402829N0741104W", Destination.GPS); - assertLatLon(40.4747, -74.1844, d1); - Destination d2 = DestinationFactory.build(mStorageService, "TEST@402829N0741104W", Destination.GPS); - assertLatLon(40.4747, -74.1844, d2); - Destination d3 = DestinationFactory.build(mStorageService, "402829S1741104E", Destination.GPS); - assertLatLon(-40.4747, 174.1844, d3); - } - private static void assertLatLon(double expectedLat, double expectedLon, Destination d) { - double lat = d.getLocation().getLatitude(); - double lon = d.getLocation().getLongitude(); - assertEquals("Latitude test fails", expectedLat, lat); - assertEquals("Longitude test fails", expectedLon, lon); - } -} diff --git a/app/src/test/java/com/ds/avare/test/DestinationTest.java b/app/src/test/java/com/ds/avare/test/DestinationTest.java deleted file mode 100644 index f8535b69b..000000000 --- a/app/src/test/java/com/ds/avare/test/DestinationTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.ds.avare.test; - -import android.content.Context; -import android.content.SharedPreferences; -import android.hardware.GeomagneticField; -import android.location.Location; -import android.preference.PreferenceManager; -import android.test.mock.MockContext; - -import com.ds.avare.R; -import com.ds.avare.StorageService; -import com.ds.avare.gps.GpsParams; -import com.ds.avare.place.Destination; -import com.ds.avare.storage.Preferences; -import com.ds.avare.utils.CalendarHelper; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import java.lang.reflect.Field; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.powermock.api.mockito.PowerMockito.mock; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.when; -import static org.powermock.api.mockito.PowerMockito.whenNew; - -/** - * Test Destination calculations - * Created by pasniak on 3/26/2017. - */ - -@RunWith(PowerMockRunner.class) -@PrepareForTest({StorageService.class, PreferenceManager.class, - GeomagneticField.class, Destination.class, CalendarHelper.class}) //classes which create the instance -public class DestinationTest { - - @Before - public void setUp() throws Exception { - - // mock the preferences used by Destination calculations - final Preferences prefs = mock(Preferences.class); - when(prefs.isSimulationMode()).thenReturn(true); - when(prefs.useBearingForETEA()).thenReturn(true); - when(prefs.getFuelBurn()).thenReturn(10.0f); - when(prefs.getDistanceUnit()).thenReturn("kt"); - when(prefs.getAircraftTAS()).thenReturn(60); - mockStatic(PreferenceManager.class); - whenNew(Preferences.class).withAnyArguments().thenReturn(prefs); - - /// mock shared preferences - final SharedPreferences sharedPrefs = mock(SharedPreferences.class); - when(sharedPrefs.getBoolean("SimulationMode", false)).thenReturn(true); - when(sharedPrefs.getString(anyString(), anyString())).thenReturn("kt"); - - final android.test.mock.MockContext ctx = mock(MockContext.class); - when(ctx.getString(R.string.DistKnot)).thenReturn("nm"); - when(PreferenceManager.getDefaultSharedPreferences((Context)isNull())) - .thenReturn(sharedPrefs); - when(PreferenceManager.getDefaultSharedPreferences((Context)any())) - .thenReturn(sharedPrefs); - - final GeomagneticField geoField = mock(GeomagneticField.class); - when(geoField.getDeclination()).thenReturn(0f); - whenNew(GeomagneticField.class).withAnyArguments().thenReturn(geoField); - } - - @Test - public void testUpdateTo() throws Exception { - - // mock storage service used to access preferences and GPS location - // in the Destination constructor - final StorageService storageService = mock(StorageService.class); - when(storageService.getApplicationContext()).thenReturn(null); - /// given the above returns null, the destination is at (0N, 0W) - Destination d = new Destination(storageService, "TEST"); - setField(d, "mFound", true); // mock that destination is found (?) - - // mock calendar set to 12:00 for ETA time calculations - when(mockCalendar.getHour()).thenReturn(12); - when(mockCalendar.getMinute()).thenReturn(0); - mockStatic(CalendarHelper.class); - when(CalendarHelper.getInstance(any(long.class))).thenReturn(mockCalendar); - - // mock GPS location - final Location loc1 = mock(Location.class); - when(loc1.getSpeed()).thenReturn(60f); - final GpsParams g = new GpsParams(loc1); - - // update destination with a location - d.updateTo(g); - - assertEquals("Wrong 0kt ETE", "00.00", d.getEte()); - assertEquals("Wrong 0kt ETA", "12:00", d.getEta()); - - // mock another GPS location, 1 degree off... - final Location loc2 = mock(Location.class); - when(loc2.getSpeed()).thenReturn(60f); - when(loc2.getLongitude()).thenReturn(1d); - when(loc2.getLatitude()).thenReturn(0d); - when(loc2.getAltitude()).thenReturn(0d); - final GpsParams g2 = new GpsParams(loc2); - - d.updateTo(g2); - - assertEquals("Wrong 60kt ETE", "01:00", d.getEte()); //... at 60kt we should be there in 1h - assertEquals("Wrong 60kt ETA", "13:00", d.getEta()); - } - - // sets a private field using reflection - private static void setField(Destination d, - String name, - Object value) throws Exception { - Field foundField = Destination.class.getDeclaredField(name); - foundField.setAccessible(true); - foundField.set(d, value); - - } - - @Mock - CalendarHelper mockCalendar; -} \ No newline at end of file diff --git a/app/src/test/java/com/ds/avare/test/HelperTest.java b/app/src/test/java/com/ds/avare/test/HelperTest.java deleted file mode 100644 index 8adc469c2..000000000 --- a/app/src/test/java/com/ds/avare/test/HelperTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.ds.avare.test; - -import com.ds.avare.utils.CalendarHelper; -import com.ds.avare.utils.Helper; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import static org.junit.Assert.assertEquals; - -/** - * Created by pasniak on 2/16/2017. - */ -@RunWith(MockitoJUnitRunner.class) -public class HelperTest { - @Test - public void testCalculateEte() throws Exception { - assertEquals(Helper.calculateEte(0, 0, 10, true), "--:--"); - assertEquals(Helper.calculateEte(0, 0, 10, false), "00.10"); - assertEquals(Helper.calculateEte(100, 100, 0, true), "01:00"); - assertEquals(Helper.calculateEte(10, 100, 0, true), "06.00"); - } - @Test - public void testPerfOfCalculateEte() throws Exception { - // 750ms on my PC - for (int dist = 0; dist < 100000; dist++) { - for (int speed = 0; speed < 100; speed++) { - String res1 = Helper.calculateEte(dist, speed, 0, true); - } - } - } - @Mock - CalendarHelper mockCalendar; - - @Test - public void testCalculateEta() throws Exception { - - Mockito.when(mockCalendar.getHour()).thenReturn(12); - Mockito.when(mockCalendar.getMinute()).thenReturn(0); - - assertEquals(Helper.calculateEta(mockCalendar, 0, 0), "--:--"); - assertEquals(Helper.calculateEta(mockCalendar, 1000, 100), "22:00"); - } - - @Test - public void testPerfOfCalculateEta() throws Exception { - // 644ms on my PC - // was 900ms when Calendar.getInstance() is called twice - CalendarHelper ch = CalendarHelper.getInstance(0); - for (int dist = 0; dist < 13000; dist++) { - for (int speed = 0; speed < 100; speed++) { - String res1 = Helper.calculateEta(ch, dist, speed); - } - } - } -} \ No newline at end of file diff --git a/app/src/test/java/com/ds/avare/test/HtmlAsserts.java b/app/src/test/java/com/ds/avare/test/HtmlAsserts.java deleted file mode 100644 index 741bf2ad5..000000000 --- a/app/src/test/java/com/ds/avare/test/HtmlAsserts.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.ds.avare.test; - -import org.xmlunit.matchers.EvaluateXPathMatcher; - -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.startsWith; - -/** - * Created by pasniak on 2/15/2017. - */ -public class HtmlAsserts { - public static void assertCells(String result, int row, String cells[]) { - String resultXml = xml(html(result)); - - for (int i = 0; i < cells.length; i++) { - assertTdEndsWith(resultXml, row, 1 + i, cells[i]); - } - } - - ///this is needed to make html fragments parsable - public static String html(String c) { return ""+c+""; } - public static String xml(String x) { return "\n" + - "\n" + - "]>\n" + x;} - - ///see https://github.com/xmlunit/xmlunit - private static void assertTdEndsWith(String result, int tr, int td, String end) { - assertThat(result, EvaluateXPathMatcher.hasXPath("//html/table/tr["+tr+"]/td["+td+"]/text()", - endsWith(end))); - } - private static void assertTdStartsWith(String result, int tr, int td, String end) { - assertThat(result, EvaluateXPathMatcher.hasXPath("//html/table/tr["+tr+"]/td["+td+"]/text()", - startsWith(end))); - } - - public static void assertRowCount (String result, int count) { - String resultXml = xml(html(result)); - assertThat(resultXml, EvaluateXPathMatcher.hasXPath("count(//html/table/tr)", - is(Integer.toString(count)))); - } - public static void assertCellCount (String result, int count) { - String resultXml = xml(html(result)); - assertThat(resultXml, EvaluateXPathMatcher.hasXPath("count(//html/table/tr/td)", - is(Integer.toString(count)))); - } -} diff --git a/app/src/test/java/com/ds/avare/test/InterfaceTest.java b/app/src/test/java/com/ds/avare/test/InterfaceTest.java deleted file mode 100644 index 72dd72b63..000000000 --- a/app/src/test/java/com/ds/avare/test/InterfaceTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.ds.avare.test; - -import android.content.Context; -import android.os.SystemClock; -import android.view.MotionEvent; -import android.webkit.WebView; - -import com.ds.avare.MainActivity; -import com.ds.avare.StorageService; -import com.ds.avare.storage.Preferences; -import com.ds.avare.utils.GenericCallback; -import com.google.common.io.Files; - -import org.junit.After; -import org.junit.Before; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.util.Enumeration; -import java.util.Scanner; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import static android.view.ViewConfiguration.getLongPressTimeout; - -/** - * Created by pasniak on 5/5/2017. - */ - -abstract class InterfaceTest { - private final static String SLASH = File.separator; - private MainActivity mMain; - private final Context mCtx = RuntimeEnvironment.application; - protected StorageService mStorageService; - protected WebView mWebView; - - private static String getCycle() throws IOException { - final String cycleUrl = "http://www.apps4av.org/new/version.php"; - final String currentCycle = new Scanner(new URL(cycleUrl).openStream(), "UTF-8").useDelimiter("\\A").next(); - if (currentCycle.isEmpty()) { throw new IOException("Unable to get cycle from "+cycleUrl); } - return currentCycle; - } - - private static String downloadDatabaseZip(Context ctx, String currentCycle, String fileName) throws IOException { - Preferences mPref = new Preferences(ctx); - final URL website = new URL(mPref.getRoot() + currentCycle + "/" + fileName); - final String avareAppDir = System.getProperty("user.dir", "./"); // sth like C:\Users\Michal\StudioProjects\avare\app\build\tmp\1705\databases.zip - final String cachedBuildFilePath = new File(avareAppDir).getAbsolutePath() - + SLASH + "build" + SLASH + "tmp" + SLASH + currentCycle + SLASH + fileName; - if (!org.codehaus.plexus.util.FileUtils.fileExists(cachedBuildFilePath)) { - System.out.println ("Creating dir " + cachedBuildFilePath); - Files.createParentDirs(new File(cachedBuildFilePath)); - System.out.println ("Downloading " + website); - ReadableByteChannel rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(cachedBuildFilePath); - long n = fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - System.out.println ("Downloaded " + n + " bytes to " + cachedBuildFilePath); - } else { - System.out.println ("Using " + cachedBuildFilePath); - } - return cachedBuildFilePath; - } - - private void unzipDb(String cachedBuildFilePath, String unzipFileNameRegex, String subDir) throws IOException { - final String roboTestDir = mMain.getFilesDir().getPath(); - final File activityFilesDir = new File(roboTestDir + (subDir=="" ? "" : SLASH + subDir)); - activityFilesDir.mkdirs(); - final ZipFile zip = new ZipFile(cachedBuildFilePath); - final Enumeration zipFileEntries = zip.entries(); - while (zipFileEntries.hasMoreElements()) { - ZipEntry zipEntry = (ZipEntry) zipFileEntries.nextElement(); - if (zipEntry.getName().matches(unzipFileNameRegex)) { - unzipFile(zip, zipEntry, zipEntry.getName(), activityFilesDir); - } - } - } - - private static void unzipFile(ZipFile zip, ZipEntry e, String unzipFileName, File toDirectory) throws IOException { - System.out.print ("Unzip " + unzipFileName + " to " + toDirectory); - BufferedInputStream bis = new BufferedInputStream(zip.getInputStream(e)); - FileOutputStream fos = new FileOutputStream(toDirectory + SLASH + unzipFileName); - int got, all = 0; - final int BUFFER_SIZE = 1024 * 8; - byte buffer[] = new byte[BUFFER_SIZE]; - while ((got = bis.read(buffer)) != -1) { - fos.write(buffer, 0, got); - all += got; - } - System.out.println (" (" + all + " bytes)"); - } - - private void deleteDb() { - final String roboTestDir = mMain.getFilesDir().getPath(); - final File activityFilesDir = new File(roboTestDir); - System.out.println ("Del " + roboTestDir); - deleteDir(activityFilesDir); - } - - private static void deleteDir(File file) { - File[] contents = file.listFiles(); - if (contents != null) { - for (File f : contents) { - deleteDir(f); - } - } - file.delete(); - } - - protected void downloadDatabases () throws IOException { - String cachedBuildFilePath = downloadDatabaseZip(mCtx, getCycle(), "databases.zip"); // download database to the build cache - unzipDb(cachedBuildFilePath, "main.db", ""); // unzip to the test directory - } - - protected void downloadWeather () throws IOException { - String weatherCachedBuildFilePath = downloadDatabaseZip(mCtx, "", "weather.zip"); // download weather to the build cache - unzipDb(weatherCachedBuildFilePath, ".*", ""); // unzip all to the test directory - } - - @Before - public void setUp() throws IOException { - mMain = Robolectric.setupActivity(MainActivity.class); - - downloadDatabases(); // all tests require FAA database - downloadMore(); // some test require more data - - prepStorageService(); - setupWebView(); - setupInterface(mCtx); // setup test-specific GUIs - } - - @After - public void tearDown() { - deleteDb(); - System.out.println(); - } - - private void setupWebView() { - mWebView = new WebView(mCtx); - } - - abstract void setupInterface(Context ctx); - - private void prepStorageService() { - mStorageService = Robolectric.setupService(StorageService.class); - mStorageService.onCreate(); - } - - // override to download data beyond base FAA data - void downloadMore () throws IOException {} - - // test helpers - protected static class MyGenericCallback extends GenericCallback { - @Override - public Object callback(Object o1, Object o2) { - return null; - } - } - - protected static MotionEvent getLongPressEvent(float x, float y) { - // simulate long press - long longPressMillis = getLongPressTimeout(); - long downTime = SystemClock.uptimeMillis(); - long eventTime = SystemClock.uptimeMillis() + longPressMillis; - int metaState = 0; - return MotionEvent.obtain( - downTime, - eventTime, - MotionEvent.ACTION_DOWN, - x, - y, - metaState - ); - } - -} diff --git a/app/src/test/java/com/ds/avare/test/LocationViewTest.java b/app/src/test/java/com/ds/avare/test/LocationViewTest.java deleted file mode 100644 index 9711a69c4..000000000 --- a/app/src/test/java/com/ds/avare/test/LocationViewTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.ds.avare.test; - -import android.content.Context; -import android.location.Location; -import android.view.MotionEvent; - -import com.ds.avare.AvareApplication; -import com.ds.avare.BuildConfig; -import com.ds.avare.LocationActivity; -import com.ds.avare.R; -import com.ds.avare.gps.GpsParams; -import com.ds.avare.touch.LongTouchDestination; -import com.ds.avare.views.LocationView; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Created by Michal on 5/5/2017. - */ - -@RunWith(RobolectricTestRunner.class) -@Config(application = AvareApplication.class) -@PowerMockIgnore({"org.mockito.", "org.robolectric."}) -public class LocationViewTest extends InterfaceTest { - - private LocationView mLocationView; - - @Test - public void smallAptPressed() throws Exception { - // set location - Location current = new Location("N51 location"); - current.setLatitude(40.582744444); - current.setLongitude(-74.736716667); - GpsParams params = new GpsParams(current); - mLocationView.initParams(params, mStorageService); - - // long press - MotionEvent motionEvent = getLongPressEvent(0,0); - mLocationView.dispatchTouchEvent(motionEvent); - - // assert destination pressed - LongTouchDestination d = mLocationView.getLongTouchDestination(); - assertEquals("N51 Airport not found", "N51", d.airport); - assertEquals("N51 Info not found", "0nm(S of) 013°", d.info); - assertNotNull("N51 SUA not found", d.sua); - assertNull("N51 TAF found", d.taf); // Solberg has no ATIS - assertNull("N51 METAR found", d.metar); - assertNull("N51 Performance found", d.performance); - HtmlAsserts.assertRowCount(d.navaids, 4); - } - - @Test - public void aptWithAtisAndTafPressed() throws Exception { - // set location - Location current = new Location("TTN location"); // TTN has ATIS and TAF - current.setLatitude(40.27617299393192); - current.setLongitude(-74.8124999934585); - GpsParams params = new GpsParams(current); - mLocationView.initParams(params, mStorageService); - - // long press - MotionEvent motionEvent = getLongPressEvent(0,0); - mLocationView.dispatchTouchEvent(motionEvent); - - // assert destination pressed - LongTouchDestination d = mLocationView.getLongTouchDestination(); - assertEquals("TTN Airport not found", "TTN", d.airport); - assertEquals("TTN Info not found", "0nm(SE of) 327°", d.info); - assertNotNull("TTN SUA not found", d.sua); - assertNotNull("TTN TAF not found", d.taf); // Trenton has TAF and ATIS - assertNotNull("TTN METAR not found", d.metar); - assertNotNull("TTN Performance not found", d.performance); - String[] perf = d.performance.split("\\n"); - assertTrue("Performance has time "+perf[0], perf[0].matches("\\d{6}Z")); - assertTrue("Performance has DA "+perf[1], perf[1].matches("Density Altitude -?\\d* ft")); - assertTrue("Performance has runway "+perf[2], perf[2].matches("Best Wind Runway \\d{2}")); - assertTrue("Performance has wind "+perf[3], perf[3].matches(" \\d{1,2}(G\\d{1,2})?KT (Head|Tail)")); - assertTrue("Performance has crosswind "+perf[4], perf[4].matches(" \\d{1,2}(G\\d{1,2})?KT (Left|Right) X")); - - HtmlAsserts.assertRowCount(d.navaids, 4); - } - - public void downloadMore () throws IOException { - downloadWeather(); - } - - public void setupInterface(Context ctx) { - final LocationActivity locationActivity = Robolectric.buildActivity(LocationActivity.class).create().get(); - mLocationView = (LocationView) locationActivity.findViewById(R.id.location); - } -} diff --git a/app/src/test/java/com/ds/avare/test/WebAppPlanInterfaceTest.java b/app/src/test/java/com/ds/avare/test/WebAppPlanInterfaceTest.java deleted file mode 100644 index 08cdc6e2a..000000000 --- a/app/src/test/java/com/ds/avare/test/WebAppPlanInterfaceTest.java +++ /dev/null @@ -1,318 +0,0 @@ -package com.ds.avare.test; - -import android.content.Context; -import android.location.Location; - -import com.ds.avare.AvareApplication; -import com.ds.avare.BuildConfig; -import com.ds.avare.place.Destination; -import com.ds.avare.place.Plan; -import com.ds.avare.webinfc.WebAppPlanInterface; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.util.ArrayList; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static org.robolectric.Shadows.shadowOf; - - -/** - * Created by pasniak on 4/1/2017. - * - * Note: if "Font not found at" error occurs this is due to a bug - * see https://github.com/robolectric/robolectric/issues/2647 - * and https://issuetracker.google.com/issues/37347564 - * A temporary workaround is to add task dependency on mergeDebugAssets - * in Run/Debug Configurations (see https://i.imgur.com/u6KSxQq.png) - */ - - -@RunWith(RobolectricTestRunner.class) -@Config(application = AvareApplication.class) -@PowerMockIgnore({"org.mockito.", "org.robolectric."}) -public class WebAppPlanInterfaceTest extends InterfaceTest { - - private WebAppPlanInterface mWebAppPlanInterface; - - @Test - public void airportSearch() throws Exception { - mWebAppPlanInterface.search("KCDW"); - assertEquals("Airport not found", "javascript:search_add('CDW','ESSEX COUNTY','Base','AIRPORT')", - getLastLoadedUrl()); - } - @Test - public void airportAdd() throws Exception { - mWebAppPlanInterface.addToPlan("CDW","Base","AIRPORT"); - assertEquals("Airport not added", 1, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void navaidSearch() throws Exception { - mWebAppPlanInterface.search("SBJ"); - assertEquals("Navaid not found", "javascript:search_add('SBJ','SOLBERG 112.90','Navaid','VOR/DME')", - getLastLoadedUrl()); - } - @Test - public void navaidAdd() throws Exception { - mWebAppPlanInterface.addToPlan("SBJ","Navaid","VOR/DME"); - assertEquals("Navaid not added", 1, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void wptSearch() throws Exception { - mWebAppPlanInterface.search("40.4747&-74.1844"); - assertEquals("User waypoint not found", "javascript:search_add('40.4747&-74.1844','GPS','GPS','GPS')", - getLastLoadedUrl()); - } - @Test - public void wptIcaoSearch() throws Exception { - mWebAppPlanInterface.search("402800N0741100W"); - assertEquals("User waypoint not found", "javascript:search_add('402800N0741100W','GPS','GPS','GPS')", - getLastLoadedUrl()); - } - @Test - public void wptAdd() throws Exception { - mWebAppPlanInterface.addToPlan("40.4747&-74.1844","GPS","GPS"); - assertEquals(1, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void wptIcaoAdd() throws Exception { - mWebAppPlanInterface.addToPlan("402800N0741100W","GPS","GPS"); - assertEquals(1, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void wptIcaoBadLatAdd() throws Exception { - // "9" cannot be in minutes/seconds - mWebAppPlanInterface.addToPlan("900000N0741000W","GPS","GPS"); - assertEquals("Able to add bad latitude degrees", 0, mStorageService.getPlan().getDestinationNumber()); - mWebAppPlanInterface.addToPlan("409000N0741000W","GPS","GPS"); - assertEquals("Able to add bad latitude minutes", 0, mStorageService.getPlan().getDestinationNumber()); - mWebAppPlanInterface.addToPlan("400090N0741000W","GPS","GPS"); - assertEquals("Able to add bad latitude seconds", 0, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void wptIcaoBadLonAdd() throws Exception { - // "9" cannot be in minutes/seconds - mWebAppPlanInterface.addToPlan("402000N1974100W","GPS","GPS"); - assertEquals("Able to add bad longitude degrees", 0, mStorageService.getPlan().getDestinationNumber()); - mWebAppPlanInterface.addToPlan("402000N0749000W","GPS","GPS"); - assertEquals("Able to add bad longitude minutes", 0, mStorageService.getPlan().getDestinationNumber()); - mWebAppPlanInterface.addToPlan("402000N0741090W","GPS","GPS"); - assertEquals("Able to add bad longitude seconds", 0, mStorageService.getPlan().getDestinationNumber()); - } - - final String PLAN1_DATA = "::::1,0,0,0,0,--:--,CDW,AIRPORT,-.-,-::::0,0,0,0,0,--:--,SBJ,VOR/DME,-.-,-:::: 0nm --:-- 360° -.-"; - final String PLAN2_DATA = "::::1,0,0,0,0,--:--,TTN,AIRPORT,-.-,-::::0,0,0,0,0,--:--,N51,AIRPORT,-.-,-:::: 0nm --:-- 360° -.-"; - final String PLAN3_DATA = "::::1,0,0,0,0,--:--,400000N0740000W,GPS,-.-,-::::0,0,0,0,0,--:--,410000N0740000W,GPS,-.-,-:::: 0nm --:-- 360° -.-"; - final String PLAN4_DATA = "::::1,0,0,0,0,--:--,40.00&-74.00,GPS,-.-,-::::0,0,0,0,0,--:--,41.00&-74.00,GPS,-.-,-:::: 0nm --:-- 360° -.-"; - - @Test - public void createLongCoast2CoastPlanFromSkyvector() throws Exception - { - // this is a copy-paste of a long plan from SkyVector, each point separated by is 1-3 spaces, 40 points total - mWebAppPlanInterface.createPlan(" 404411N0742217W N05 TIKLE 8PA0 405409N0771129W 405747N0774906W 405908N0790430W DISHE ACO KC66S 405658N0822541W 405710N0825236W 405242N0833633W 405000N0841346W 404755N0844847W 404808N0853707W 404833N0861950W 404706N0871226W 404603N0875932W 404352N0883612W 404352N0892237W 404321N0902506W 404308N0913949W 403858N0925716W 403808N0943536W 403507N0964408W 403327N0975223W 403339N0994109W 403231N1010935W 403134N1025146W 402512N1041434W 402646N1054358W 402204N1074645W AWLIJ 401901N1104801W 401032N1130326W 401110N1152501W 401110N1175116W 400948N1203157W 400858N1224827W "); - assertEquals("Long SkyVector plan creation failed", 40, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void createLongMEtoFLPlanFromSkyVector() throws Exception - { - // this is a copy-paste of a long plan along the E coast from SkyVector, each point separated by is 1-3 spaces, 24 points total - // https://skyvector.com/?ll=36.279707198143875,-87.7340698184927&chart=301&zoom=11&fpl=%20KFSO%204448N07307W%20BJA%204413N07315W%204351N07320W%204328N07343W%204244N07357W%204215N07403W%204139N07420W%204043N07433W%203943N07510W%203847N07549W%203724N07643W%203623N07642W%203544N07704W%203426N07848W%203322N07951W%203229N08119W%203054N08150W%202956N08148W%20KORL%202730N08110W%202638N08058W%202600N08046W%202516N08055W - mWebAppPlanInterface.createPlan(" 444758N0730707W BJA 441254N0731432W 435111N0732001W 432808N0734322W 424401N0735633W 421451N0740309W 413906N0741954W 404305N0743232W 394319N0750953W 384710N0754910W 372410N0764300W 362253N0764227W 354429N0770352W 342549N0784741W 332146N0795125W 322844N0811845W 305405N0814931W 295549N0814752W KORL 272937N0810958W 263815N0805809W 260021N0804531W 251546N0805524W "); - assertEquals("Long SkyVector plan creation failed", 24, mStorageService.getPlan().getDestinationNumber()); - assertMinMaxCoords(mStorageService.getPlan(), 45, 25, -82, -73); - } - - @Test - public void createAndLoadPlans() throws Exception { - String data; - - // create plan 1 with 2 points - mWebAppPlanInterface.createPlan("KCDW SBJ"); - assertEquals(2, mStorageService.getPlan().getDestinationNumber()); - data = mWebAppPlanInterface.getPlanData(); - final String PLAN1_DATA = "::::1,0,0,0,0,--:--,CDW,AIRPORT,-.-,-::::0,0,0,0,0,--:--,SBJ,VOR/DME,-.-,-:::: 0nm --:-- 360° -.-"; - assertEquals(PLAN1_DATA, data); - - //save it - mWebAppPlanInterface.savePlan("TEST1"); //to preferences - assertUrl("javascript:set_plan_count('1 - 1 of 1')"); - - // refresh - mWebAppPlanInterface.refreshPlanList(); - assertUrl("javascript:set_plan_count('1 - 1 of 1')"); - - // clean up the current plan - mWebAppPlanInterface.discardPlan(); - - - // create plan 2 - mWebAppPlanInterface.createPlan("KTTN N51"); - assertEquals(2, mStorageService.getPlan().getDestinationNumber()); - assertUrl("javascript:plan_add('N51','Base','SOLBERG-HUNTERDON')"); - - //save it to preferences - mWebAppPlanInterface.savePlan("TEST2"); - assertUrl("javascript:set_plan_count('1 - 2 of 2')"); - - // clean up the current plan - mWebAppPlanInterface.discardPlan(); - - - // create plan 3 with ICAO style coordinates - mWebAppPlanInterface.createPlan("400000N0740000W 410000N0740000W"); - assertEquals(2, mStorageService.getPlan().getDestinationNumber()); - assertUrl("javascript:plan_add('410000N0740000W','GPS','GPS')"); - - //save it to preferences - mWebAppPlanInterface.savePlan("TEST3"); - assertUrl("javascript:set_plan_count('1 - 3 of 3')"); - - // clean up the current plan - mWebAppPlanInterface.discardPlan(); - - - // create plan 4 with Google style coordinates - mWebAppPlanInterface.createPlan("40.00&-74.00 41.00&-74.00"); - assertEquals(2, mStorageService.getPlan().getDestinationNumber()); - assertUrl("javascript:plan_add('41.00&-74.00','GPS','GPS')"); - - //save it to preferences - mWebAppPlanInterface.savePlan("TEST4"); - assertUrl("javascript:set_plan_count('1 - 4 of 4')"); - - // clean up the current plan - mWebAppPlanInterface.discardPlan(); - - - // refresh - ArrayList plans = mWebAppPlanInterface.getPlanNames(10); - assertEquals("TEST1", plans.get(0)); - assertEquals("TEST2", plans.get(1)); - assertEquals("TEST3", plans.get(2)); - assertEquals("TEST4", plans.get(3)); - - //now retrieve plan 1 to N51 - mWebAppPlanInterface.loadPlan("TEST1"); - data = mWebAppPlanInterface.getPlanData(); - assertEquals("TEST1" + PLAN1_DATA, data); - assertUrl("javascript:plan_add('SBJ','Navaid','SOLBERG 112.90')"); - - // filter out the second plan - mWebAppPlanInterface.planFilter("2"); - assertUrl("javascript:set_plan_count('1 - 1 of 1')"); - - //now retrieve plan 2 to N51 - mWebAppPlanInterface.loadPlan("TEST2"); - data = mWebAppPlanInterface.getPlanData(); - assertEquals("TEST2" + PLAN2_DATA, data); - assertUrl("javascript:plan_add('N51','Base','SOLBERG-HUNTERDON')"); - - - //now retrieve plan 3 to 410000N0740000W - mWebAppPlanInterface.loadPlan("TEST3"); - data = mWebAppPlanInterface.getPlanData(); - assertEquals("TEST3" + PLAN3_DATA, data); - assertUrl("javascript:plan_add('410000N0740000W','GPS','GPS')"); - - - //now retrieve plan 4 to 41.00&-74.00 - mWebAppPlanInterface.loadPlan("TEST4"); - data = mWebAppPlanInterface.getPlanData(); - assertEquals("TEST4" + PLAN4_DATA, data); - assertUrl("javascript:plan_add('41.00&-74.00','GPS','GPS')"); - } - - private void assertUrl(String expected) { - String lastLoadedUrl = shadowOf(mWebView).getLastLoadedUrl(); // get state of the JS engine - assertEquals(expected, lastLoadedUrl); - } - - private String getLastLoadedUrl() { - return shadowOf(mWebView).getLastLoadedUrl(); - } - - @Test - public void createPlanWithUserWpt() throws Exception { - mWebAppPlanInterface.createPlan("KCDW SBJ 40.4747&-74.1844 410000N0740000W"); - assertEquals(4, mStorageService.getPlan().getDestinationNumber()); - } - @Test - public void createPlanWithAirway() throws Exception { - mWebAppPlanInterface.createPlan("SLATT V6 EMPYR V6 LGA"); - assertEquals(13, mStorageService.getPlan().getDestinationNumber()); - } - - @Test - public void planOperations() throws Exception { - // create plan with points spaced 1 degree lattitude = 60mn/log - final String PLAN = "40.00&-74.00 41.00&-74.00 42.00&-74.00 43.00&-74.00 44.00&-74.00 45.00&-74.00"; - final int nbWaypoints = 6; - - mWebAppPlanInterface.createPlan(PLAN); - assertEquals(nbWaypoints, mStorageService.getPlan().getDestinationNumber()); - - final Plan p = mStorageService.getPlan(); - - assertEquals("Plan track shape", (nbWaypoints-1)*4, p.getTrackShape().getNumCoords()); - - for (int i = 0; i < nbWaypoints ; i++) { - assertFalse("Check for not passed destinations", p.isPassed(i)); - assertEquals("Bearing calcs", 0, p.getBearing(i, i+1), 0.1); - } - assertNull("Access beyond last destination", p.getDestination(100)); - - p.simulate(); - assertEquals(300, p.getDistance(), 1); // so roughly 300nm - - mWebAppPlanInterface.activateToggle(); - p.simulate(); - - int lastLeg = nbWaypoints-1; - assertEquals("Last leg fuel calculation", "6.0", p.getDestination(lastLeg).getFuel()); // at default 10 gal/hour - - // add and remove waypoint - mWebAppPlanInterface.addToPlan("46.00&-74.00","GPS","GPS"); - assertEquals("Waypoint add", nbWaypoints+1, mStorageService.getPlan().getDestinationNumber()); - assertEquals("Plan track shape", (nbWaypoints-1)*4 + 4, p.getTrackShape().getNumCoords()); - - // check geosearch - int found = p.findClosePointId(-74, 46, 2); - assertEquals("Finding by position", lastLeg+1, found); - - p.remove(lastLeg+1); - p.simulate(); - assertEquals("Leg add and remove", 300, p.getDistance(), 1); - - p.advance(); - assertTrue("Check for passed destinations", p.isPassed(0)); - for (int i = 1; i < nbWaypoints ; i++) { - assertFalse("Check for not passed destinations failed", p.isPassed(i)); - } - - p.makeInactive(); - assertFalse("Deactivation failed", p.isActive()); - } - - public void setupInterface(Context ctx) { - mWebAppPlanInterface = new WebAppPlanInterface(ctx, mWebView, new MyGenericCallback()); - mWebAppPlanInterface.connect(mStorageService); - } - - private static void assertMinMaxCoords(Plan p, int latMax, int latMin, int lonMin, int lonMax) { - for (int i = 0; i < p.getDestinationNumber(); i++) { - Destination d = p.getDestination(i); - Location l = d.getLocation(); - double lat = l.getLatitude(), lon = l.getLongitude(); - assertTrue("Lat conversion "+lat+" failed at " + d.getStorageName(), latMax > lat && lat > latMin); - assertTrue("Lon conversion "+lon+" failed at "+ d.getStorageName(), lonMin < lon && lon < lonMax); - } - } - -} diff --git a/app/src/test/java/com/ds/avare/test/WindsAloftHelperTest.java b/app/src/test/java/com/ds/avare/test/WindsAloftHelperTest.java deleted file mode 100644 index f6b039f4c..000000000 --- a/app/src/test/java/com/ds/avare/test/WindsAloftHelperTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.ds.avare.test; - -import com.ds.avare.utils.WindsAloftHelper; -import com.ds.avare.weather.WindsAloft; - -import org.junit.Before; -import org.junit.Test; - -import static com.ds.avare.test.HtmlAsserts.assertCellCount; -import static com.ds.avare.test.HtmlAsserts.assertCells; -import static com.ds.avare.test.HtmlAsserts.assertRowCount; -import static junit.framework.Assert.assertEquals; - - -/** - * Created by pasniak on 2/12/2017. - * tests all public interfaces of WindsAloftHelper: - * formatWindsHTML - * DirSpeed.parseFrom - */ -public class WindsAloftHelperTest { - - @Before - public void setUp() throws Exception { - wa = new WindsAloft(); - } - - // test winds/temps parsing - @Test - public void testDirSpeedInterface() throws Exception { - WindsAloftHelper.DirSpeed wind = WindsAloftHelper.DirSpeed.parseFrom("1430"); - assertEquals(wind.Dir, 140); - assertEquals(wind.Speed, 30); - } - - @Test - public void testDirSpeedInterfaceWithTemps() throws Exception { - WindsAloftHelper.DirSpeed wind = WindsAloftHelper.DirSpeed.parseFrom("1212+00"); - assertEquals(wind.Dir, 120); - assertEquals(wind.Speed, 12); - } - - @Test (expected=StringIndexOutOfBoundsException.class) - public void testDirSpeedInterfaceWithBadData1() throws Exception { - WindsAloftHelper.DirSpeed wind = WindsAloftHelper.DirSpeed.parseFrom("+00"); - } - - @Test (expected=StringIndexOutOfBoundsException.class) - public void testDirSpeedInterfaceWithBadData2() throws Exception { - WindsAloftHelper.DirSpeed wind = WindsAloftHelper.DirSpeed.parseFrom("+"); - } - - @Test (expected=NumberFormatException.class) - public void testDirSpeedInterfaceGarbage() throws Exception { - WindsAloftHelper.DirSpeed.parseFrom("garbage"); - } - - // test output table formatting - @Test - public void testEmpty3000() throws Exception { - wa.w3k = ""; - wa.w6k = "2837+02"; - String result = WindsAloftHelper.formatWindsHTML(wa, 6); - - assertRowCount(result, 2); - } - - @Test - public void testLevel3000() throws Exception { - wa.w3k = "1430"; - String result = WindsAloftHelper.formatWindsHTML(wa, 3); - - assertCells(result, 1, new String[] {"3000" , "140°" ,"30kt"}); - } - - @Test - public void testLevel6000() throws Exception { - wa.w3k = "1430"; - wa.w6k = "2837+02"; - String result = WindsAloftHelper.formatWindsHTML(wa, 6); - - assertCells(result, 2, new String[] {"6000", "280°", "37kt", "2C"}); - } - - @Test - public void testLightAndVariable() throws Exception { - wa.w3k = "9900"; - wa.w6k = "9900-01"; - - String result = WindsAloftHelper.formatWindsHTML(wa, 6); - - assertCells(result, 1, new String[] {"3000", "0°", "0kt"}); - assertCells(result, 2, new String[] {"6000", "0°", "0kt", "-1C"}); - } - - - @Test - public void testAbove100kt() throws Exception { - wa.w3k = "780061"; - wa.w6k = "850459"; - String result = WindsAloftHelper.formatWindsHTML(wa, 6); - - assertCells(result, 1, new String[] {"3000", "280°", "100kt", "-61C"}); - assertCells(result, 2, new String[] {"6000", "350°", "104kt", "-59C"}); - } - - private void SetupFullTable() { - String[] winds = "9900 0214+13 3609+09 3410+03 3120-09 3126-22 292739 293148 772457".split(" "); - wa.w3k = winds[0]; - wa.w6k = winds[1]; - wa.w9k = winds[2]; - wa.w12k = winds[3]; - wa.w18k = winds[4]; // show up to this level so 5 rows - wa.w24k = winds[5]; - wa.w30k = winds[6]; - wa.w34k = winds[7]; - wa.w39k = winds[8]; - } - - @Test - public void TestFullTableUpTo18() throws Exception { - SetupFullTable(); - String result = WindsAloftHelper.formatWindsHTML(wa, 18); // up to w18k - - assertRowCount(result, 5); // 5 rows (3,6,9,12,18) up to w18k - assertCellCount(result, 5*4); - } - - @Test - public void TestFullTable() throws Exception { - SetupFullTable(); - String result = WindsAloftHelper.formatWindsHTML(wa, 39); - - assertRowCount(result, 9); // all 9 rows - assertCellCount(result, 9*4); - } - - @Test - public void testGarbage() throws Exception { - wa.w3k = "garbage"; - wa.w6k = "1212--"; // garbage temperature, parsing fails - wa.w9k = "12345678"; - wa.w12k = "+00"; // short string - String result = WindsAloftHelper.formatWindsHTML(wa, 12); - - assertRowCount(result, 4); - } - - private WindsAloft wa; -} \ No newline at end of file diff --git a/extra/mamba/put_tenmin.sh b/extra/mamba/put_tenmin.sh index 0b28ff088..383279eb4 100755 --- a/extra/mamba/put_tenmin.sh +++ b/extra/mamba/put_tenmin.sh @@ -19,8 +19,4 @@ ./gentfr.sh -mv TFRs.zip weather.zip conus.zip ~/www - - -sshpass -f "/home/bitnami/.ssh/mamba.passwd" scp /home/bitnami/www/*.zip apps4av@apps4av.org:/home/apps4av/mamba.dreamhosters.com/new diff --git a/extra/mamba/radar.py b/extra/mamba/radar.py index 0aab07472..33ade0616 100755 --- a/extra/mamba/radar.py +++ b/extra/mamba/radar.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 #Copyright (c) 2021, Apps4Av Inc. (apps4av@gmail.com) #All rights reserved. # @@ -13,7 +13,7 @@ # import requests -import bs4 +from bs4 import BeautifulSoup import re import os import datetime @@ -25,7 +25,7 @@ url = 'https://mrms.ncep.noaa.gov/data/RIDGEII/L2/CONUS/CREF_QCD/' #parse html -soup = bs4.BeautifulSoup(requests.get(url).text, 'lxml') +soup = BeautifulSoup(requests.get(url).text, 'lxml') table = soup.find('table') listOfFiles = [] @@ -37,7 +37,7 @@ floc = td[0].find('a').get('href') if None != floc: # parse time out of file names - fm = re.search("^CONUS_L2_CREF_QCD_(\d*)_(\d*).tif.gz$", floc) + fm = re.search("^CONUS_L2_CREF_QCD_([0-9]*)_([0-9]*).tif.gz$", floc) if None != fm: datetimeStr = fm.group(1) + fm.group(2) d = {} diff --git a/extra/mamba/tfr.pl b/extra/mamba/tfr.pl index 36922a25d..b9d680241 100755 --- a/extra/mamba/tfr.pl +++ b/extra/mamba/tfr.pl @@ -16,10 +16,12 @@ use strict; use warnings; use LWP::Simple; +use LWP::UserAgent; use HTML::LinkExtor; use XML::Parser; use File::stat; use Time::localtime; +use JSON qw( decode_json ); my $printit = 0; @@ -33,17 +35,6 @@ my $printuomlo = 0; my $tfr = ""; my @linksXml; -sub cb { - my($tag, %links) = @_; - my $lk = "@{[%links]}\n"; - if ($lk and (-1 != index($lk, "save_pages"))) { -#replace html to xml to get XML - $lk =~ s/\.html/.xml/g; - $lk =~ s/href\s*//g; -#put in an array - push(@linksXml, $lk); - } -} # The Handlers sub hdl_start{ @@ -176,17 +167,22 @@ sub hdl_char { }); # get TFR list -my $response = get('http://tfr.faa.gov/tfr2/list.html') or die; -# extract links from it -my $LX = new HTML::LinkExtor(\&cb, 'http://tfr.faa.gov/'); -$LX->parse($response); -#throw away duplicate links -my %hash = map { $_ => 1 } @linksXml; -my @unique = keys %hash; +my $ua = LWP::UserAgent->new( + ssl_opts => { verify_hostname => 0 }, +); +my $req = new HTTP::Request('GET','https://tfr.faa.gov/tfrapi/getTfrList'); +my $response = $ua->request($req) or die; +my $decoded_json = decode_json($response->content()); +for my $notam (@$decoded_json) { + my $nid = $notam->{"notam_id"}; + my $nn = $nid =~ s/\//_/r; + push(@linksXml, "https://tfr.faa.gov/download/detail_" . $nn . ".xml"); +} # Now process each TFR XML -for my $url( @{unique} ) { +for my $url( @{linksXml} ) { $url =~ s/\/\.\.\//\//g; - if(my $data_xml = get($url)) { + my $req = new HTTP::Request('GET', $url); + if(my $data_xml = $ua->request($req)->content()) { $parser->parse($data_xml); } }