funaishi_yuuki@funkit.co.jp 10 månader sedan
incheckning
eea834668b
86 ändrade filer med 7250 tillägg och 0 borttagningar
  1. 45 0
      README.md
  2. 1 0
      app/.gitignore
  3. 34 0
      app/build.gradle
  4. BIN
      app/libs/commons-net-3.6.jar
  5. 21 0
      app/proguard-rules.pro
  6. 20 0
      app/release/output-metadata.json
  7. 55 0
      app/src/main/AndroidManifest.xml
  8. 262 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/MyApplication.java
  9. 391 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/NoticeDialogFragment.java
  10. 37 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/BaseActivity.java
  11. 529 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/CourseSettingsActivity.java
  12. 813 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/MainActivity.java
  13. 324 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/SplashActivity.java
  14. 526 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/SystemSettingsActivity.java
  15. 112 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/adapter/BusRouteAdapter.java
  16. 81 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/busRoute_info.java
  17. 163 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/busRoute_infos.java
  18. 106 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/course_settings.java
  19. 253 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/drive_state.java
  20. 47 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/location_send.java
  21. 196 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/system_settings.java
  22. 317 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/service/DriveService.java
  23. 243 0
      app/src/main/java/jp/co/ecosysnetwork/ccloca_app/thread/CommThread.java
  24. 246 0
      app/src/main/java/jp/co/ecosysnetwork/universal/EsnDebugLog.java
  25. 155 0
      app/src/main/java/jp/co/ecosysnetwork/universal/EsnFileSearch.java
  26. 64 0
      app/src/main/java/jp/co/ecosysnetwork/universal/EsnQueue.java
  27. 305 0
      app/src/main/java/jp/co/ecosysnetwork/universal/EsnUtility.java
  28. 129 0
      app/src/main/java/jp/co/ecosysnetwork/universal/EsnXmlParser.java
  29. 28 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  30. 68 0
      app/src/main/res/drawable/bg_button.xml
  31. 13 0
      app/src/main/res/drawable/bg_data.xml
  32. 13 0
      app/src/main/res/drawable/bg_edittext.xml
  33. 13 0
      app/src/main/res/drawable/bg_frame.xml
  34. 13 0
      app/src/main/res/drawable/bg_frame_warn.xml
  35. 5 0
      app/src/main/res/drawable/ic_add_small_normal.xml
  36. 6 0
      app/src/main/res/drawable/ic_back_activity.xml
  37. 11 0
      app/src/main/res/drawable/ic_back_activity_normal.xml
  38. 11 0
      app/src/main/res/drawable/ic_back_activity_press.xml
  39. 10 0
      app/src/main/res/drawable/ic_bas_stop.xml
  40. 11 0
      app/src/main/res/drawable/ic_cancel_normal.xml
  41. 5 0
      app/src/main/res/drawable/ic_clear_small_normal.xml
  42. 11 0
      app/src/main/res/drawable/ic_close_normal.xml
  43. 5 0
      app/src/main/res/drawable/ic_comm_disable.xml
  44. 5 0
      app/src/main/res/drawable/ic_comm_enable.xml
  45. 10 0
      app/src/main/res/drawable/ic_import.xml
  46. 36 0
      app/src/main/res/drawable/ic_launcher_background.xml
  47. 5 0
      app/src/main/res/drawable/ic_location_disable.xml
  48. 5 0
      app/src/main/res/drawable/ic_location_enable.xml
  49. 11 0
      app/src/main/res/drawable/ic_next_activity_normal.xml
  50. 11 0
      app/src/main/res/drawable/ic_ok_normal.xml
  51. 10 0
      app/src/main/res/drawable/ic_save.xml
  52. 9 0
      app/src/main/res/drawable/ic_send_mail_listview.xml
  53. 199 0
      app/src/main/res/layout/activity_course_settings.xml
  54. 194 0
      app/src/main/res/layout/activity_main.xml
  55. 131 0
      app/src/main/res/layout/activity_splash.xml
  56. 292 0
      app/src/main/res/layout/activity_system_settings.xml
  57. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  58. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  59. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  60. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  61. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  62. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  63. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  64. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  65. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  66. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  67. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  68. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  69. BIN
      app/src/main/res/raw/anzen.wav
  70. BIN
      app/src/main/res/raw/bstop.wav
  71. BIN
      app/src/main/res/raw/cose.wav
  72. BIN
      app/src/main/res/raw/error.wav
  73. BIN
      app/src/main/res/raw/opening.wav
  74. BIN
      app/src/main/res/raw/otsukare.wav
  75. BIN
      app/src/main/res/raw/tochaku.wav
  76. 113 0
      app/src/main/res/values/colors.xml
  77. 114 0
      app/src/main/res/values/dimens.xml
  78. 80 0
      app/src/main/res/values/strings.xml
  79. 10 0
      app/src/main/res/values/styles.xml
  80. 24 0
      build.gradle
  81. 19 0
      gradle.properties
  82. BIN
      gradle/wrapper/gradle-wrapper.jar
  83. 6 0
      gradle/wrapper/gradle-wrapper.properties
  84. 172 0
      gradlew
  85. 84 0
      gradlew.bat
  86. 2 0
      settings.gradle

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+**Edit a file, create a new file, and clone from Bitbucket in under 2 minutes**
+
+When you're done, you can delete the content in this README and update the file with details for others getting started with your repository.
+
+*We recommend that you open this README in another tab as you perform the tasks below. You can [watch our video](https://youtu.be/0ocf7u76WSo) for a full demo of all the steps in this tutorial. Open the video in a new tab to avoid leaving Bitbucket.*
+
+---
+
+## Edit a file
+
+You’ll start by editing this README file to learn how to edit a file in Bitbucket.
+
+1. Click **Source** on the left side.
+2. Click the README.md link from the list of files.
+3. Click the **Edit** button.
+4. Delete the following text: *Delete this line to make a change to the README from Bitbucket.*
+5. After making your change, click **Commit** and then **Commit** again in the dialog. The commit page will open and you’ll see the change you just made.
+6. Go back to the **Source** page.
+
+---
+
+## Create a file
+
+Next, you’ll add a new file to this repository.
+
+1. Click the **New file** button at the top of the **Source** page.
+2. Give the file a filename of **contributors.txt**.
+3. Enter your name in the empty file space.
+4. Click **Commit** and then **Commit** again in the dialog.
+5. Go back to the **Source** page.
+
+Before you move on, go ahead and explore the repository. You've already seen the **Source** page, but check out the **Commits**, **Branches**, and **Settings** pages.
+
+---
+
+## Clone a repository
+
+Use these steps to clone from SourceTree, our client for using the repository command-line free. Cloning allows you to work on your files locally. If you don't yet have SourceTree, [download and install first](https://www.sourcetreeapp.com/). If you prefer to clone from the command line, see [Clone a repository](https://confluence.atlassian.com/x/4whODQ).
+
+1. You’ll see the clone button under the **Source** heading. Click that button.
+2. Now click **Check out in SourceTree**. You may need to create a SourceTree account or log in.
+3. When you see the **Clone New** dialog in SourceTree, update the destination path and name if you’d like to and then click **Clone**.
+4. Open the directory you just created to see your repository’s files.
+
+Now that you're more familiar with your Bitbucket repository, go ahead and add a new file locally. You can [push your change back to Bitbucket with SourceTree](https://confluence.atlassian.com/x/iqyBMg), or you can [add, commit,](https://confluence.atlassian.com/x/8QhODQ) and [push from the command line](https://confluence.atlassian.com/x/NQ0zDQ).

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 34 - 0
app/build.gradle

@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.2"
+
+    defaultConfig {
+        applicationId "jp.co.ecosysnetwork.ccloca_app"
+        minSdkVersion 29
+        targetSdkVersion 30
+        versionCode 16
+        versionName "2.14"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    implementation files('libs/commons-net-3.6.jar')
+    implementation 'com.google.android.gms:play-services-location:17.1.0'
+}

BIN
app/libs/commons-net-3.6.jar


+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 20 - 0
app/release/output-metadata.json

@@ -0,0 +1,20 @@
+{
+  "version": 1,
+  "artifactType": {
+    "type": "APK",
+    "kind": "Directory"
+  },
+  "applicationId": "jp.co.ecosysnetwork.ccloca_app",
+  "variantName": "release",
+  "elements": [
+    {
+      "type": "SINGLE",
+      "filters": [],
+      "properties": [],
+      "versionCode": 16,
+      "versionName": "2.14",
+      "enabled": true,
+      "outputFile": "app-release.apk"
+    }
+  ]
+}

+ 55 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="jp.co.ecosysnetwork.ccloca_app">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        tools:ignore="ScopedStorage" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
+    <application
+        android:name=".MyApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        android:usesCleartextTraffic="true"
+        tools:ignore="AllowBackup">
+        <service android:name=".service.DriveService" />
+<!--        <service android:name=".service.CommService"-->
+<!--                 android:permission="android.permission.BIND_JOB_SERVICE"/>-->
+
+        <activity
+            android:name=".activity.MainActivity"
+            android:launchMode="singleInstance"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name=".activity.CourseSettingsActivity"
+            android:launchMode="singleInstance"
+            android:screenOrientation="landscape" />
+        <activity
+            android:name=".activity.SystemSettingsActivity"
+            android:launchMode="singleInstance"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name=".activity.SplashActivity"
+            android:launchMode="singleInstance"
+            android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 262 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/MyApplication.java

@@ -0,0 +1,262 @@
+package jp.co.ecosysnetwork.ccloca_app;
+
+import android.app.Application;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Environment;
+
+import java.util.Locale;
+
+import jp.co.ecosysnetwork.ccloca_app.models.drive_state;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+/**
+ * 自身のアプリケーションクラス
+ */
+public class MyApplication extends Application {
+    //region ディレクトリ
+    /**
+     * ログファイルディレクトリ
+     */
+    public static final String LOG_DIRECTORY = "/Log/";
+    // endregion
+    //region プリファレンス
+    /**
+     * プリファレンスの名称
+     */
+    public static final String PREFERENCE_NAME = "ccloca_app_prf";
+    /**
+     * プリファレンスキー名称(ログファイル最終送信日)
+     */
+    public static final String PKEY_LOG_LAST_SEND_DATE = "LogLastSendDate";
+    /**
+     * プリファレンスキー名称(サーバURL)
+     */
+    public static final String PKEY_SERVER_URL = "ServerUrl";
+    /**
+     * プリファレンスキー名称(更新間隔)
+     */
+    public static final String PKEY_UPDATE_CYCLE_MSEC = "UpdateCycleMsec";
+    /**
+     * プリファレンスキー名称(接近判定)
+     */
+    public static final String PKEY_APPROACH_JUDGE_METER = "ApproachJudgeMeter";
+    /**
+     * プリファレンスキー名称(形態端末ID)
+     */
+    public static final String PKEY_PHONE_ID = "PhoneId";
+    /**
+     * プリファレンスキー名称(運行設定されているコースのinfo_wkey。 %d:運行順インデックス)
+     */
+    public static final String PKEY_COURSE_SETTINGS_KEY = "CourseSettingsKey_%d";
+    /**
+     * プリファレンスキー名称(コース情報の個数)
+     */
+    public static final String PKEY_COURSE_INFO_NUM = "CourseInfoNum";
+    /**
+     * プリファレンスキー名称(コース情報>コースの表示順。 %d:コースインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_DISP_ORDER = "CourseInfoDispOrder_%d";
+    /**
+     * プリファレンスキー名称(コース情報>コースキー。 %d:コースインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_INFO_WKEY = "CourseInfoInfoWkey_%d";
+    /**
+     * プリファレンスキー名称(コース情報>コース名。 %d:コースインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_INFO_WNAME = "CourseInfoInfoWname_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントの個数。 %d:コースインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_NUM = "CourseInfoPointNum_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントの順番。 %d:コースインデックス %d:通過ポイントインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_STEP = "CourseInfoPoint_%d_Step_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントのキー。 %d:コースインデックス %d:通過ポイントインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_INFO_MKEY = "CourseInfoPoint_%d_InfoMkey_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントの名称。 %d:コースインデックス %d:通過ポイントインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_INFO_MNAME = "CourseInfoPoint_%d_InfoMname_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントの緯度。 %d:コースインデックス %d:通過ポイントインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_INFO_MLAT = "CourseInfoPoint_%d_InfoMlat_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントの経度。 %d:コースインデックス %d:通過ポイントインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_INFO_MLON = "CourseInfoPoint_%d_InfoMlon_%d";
+    /**
+     * プリファレンスキー名称(コース情報>通過ポイントのメール送信有無。 %d:コースインデックス %d:通過ポイントインデックス)
+     */
+    public static final String PKEY_COURSE_INFO_POINT_INFO_ML = "CourseInfoPoint_%d_InfoMl_%d";
+    /**
+     * プリファレンスキー名称(最終運転日)
+     */
+    public static final String PKEYlast_update_course_date = "last_drive_date";
+    /**
+     * プリファレンスキー名称(現在のコースインデックス)
+     */
+    public static final String PKEY_NOW_COURSE_INDEX = "now_course_index";
+    /**
+     * プリファレンスキー名称(運行状態)
+     */
+    public static final String PKEY_OPERATION_STATE = "operation_state";
+    /**
+     * プリファレンスキー名称(接近情報リスト)
+     */
+    public static final String PKEY_POINT_APPROACHS = "point_approachs";
+    /**
+     * プリファレンスキー名称(コース情報>コースのID。 %d:コースインデックス)
+     */
+    public static final String PKEY_BUS_ROUTE_INFO_ID = "BusRouteInfoID_%d";
+    /**
+     * プリファレンスキー名称(コース情報>コース名称。 %d:コースインデックス)
+     */
+    public static final String PKEY_BUS_ROUTE_INFO_NAME = "BusRouteInfoName_%d";
+    /**
+     * リファレンスキー名称(選択中コースID)
+     *
+     */
+    public static final String PKEY_SELECTED_BUS_ROUTE_INFO_ID = "SelectedBusRouteInfoID";
+    /**
+     * プリファレンスキー名称(選択中コース名称)
+     */
+    public static final String PKEY_SELECTED_BUS_ROUTE_INFO_NAME = "SelectedBusRouteInfoName";
+    /**
+     * プリファレンスキーポジション(選択中コースポジション)
+     */
+    public static final String PKEY_SELECTED_BUS_ROUTE_INFO_POSITION = "SelectedBusRouteInfoPosition";
+    // endregion
+    //region パスワード
+    /**
+     * コース設定のパスワード
+     */
+    private static final String PASSWORD_COURSE_SETTINGS = "12345";
+    /**
+     * システム設定のパスワード
+     */
+    private static final String PASSWORD_SYSTEM_SETTINGS = "ecosys";
+    // endregion
+    //region APIのURL
+    /**
+     * コース情報取得API
+     */
+    public static final String APIURL_GET_COURSE = "/getBusRouteListFromPhoneNo";
+    /**
+     * メール送信API
+     */
+    public static final String APIURL_GET_SEND_MAIL  = "/system/_locate.php?id=%s&ml=%s&mc=%d&wkey=%s&fg=%d";
+    /**
+     * 位置情報送信API
+     */
+    public static final String APIURL_GET_SEND_POS = "/system/_locate.php?id=%s&la=%.9f&ln=%.10f&al=%.10f&dy=%s&tm=%s&ag=%d&ck=&ac=%d&st=%d&fg=%d&wkey=%s";
+    /**
+     * 携帯端末チェックAPI
+     */
+    public static final String APIURL_CHECK_PHONE_ID = "/checkPhoneId";
+    /**
+     * 運行開始API
+     */
+    public static final String APIURL_START_BUS_COURSE = "/startBusCourse";
+    /**
+     * 運行終了API
+     */
+    public static final String APIURL_END_BUS_COURSE = "/endBusCourse";
+    /**
+     * 運行中位置情報送信API
+     */
+    public static final String APIURL_SEND_BUS_LOCATION = "/sendDeliveryNotification";
+    //endregion
+    //region ApiKey
+    /**
+     *  サーバにAPIを送る際のKey
+     */
+    public static final String API_KEY = "AIY4KPTHfnEcdyd8Rkii8RRmQYEaCYz7c8KTexc";
+    //endregion
+
+    //region メンバ変数
+    /**
+     * シングルトン
+     */
+    private static MyApplication _instance = null;
+    /**
+     * 運転状態モデル
+     */
+    public drive_state drive_state_model;
+    // endregion
+    //region 外部公開メンバ変数(getInstance()メソッドを通してアクセスすること)
+    // endregion
+    //region イベント
+    /**
+     * アプリケーション作成時のイベント
+     * Application#onCreateは、ActivityやServiceが生成される前に呼ばれる。
+     */
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        _instance = this;
+        drive_state_model = new drive_state(_instance.getApplicationContext().getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE));
+    }
+    // endregion
+    //region プロパティ
+    /**
+     * アプリケーションのインスタンスを返す
+     *
+     * @return アプリケーションのインスタンス
+     */
+    public static MyApplication getInstance() {
+        return _instance;
+    }
+    // endregion
+    //region 外部公開メソッド
+    /**
+     * ログ出力ディレクトリの取得
+     * @return ログ出力ディレクトリ
+     */
+    public String getLogDirectory() {
+        return _instance.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + LOG_DIRECTORY;
+    }
+    /**
+     * コース設定パスワードが一致するかの判定
+     * @param inpPass 入力パスワード
+     * @return true:一致 false:不一致
+     */
+    public boolean isOkCourseSettingPassword(String inpPass) {
+        return PASSWORD_COURSE_SETTINGS.equals(inpPass);
+    }
+    /**
+     * システム設定パスワードが一致するかの判定
+     * @param inpPass 入力パスワード
+     * @return true:一致 false:不一致
+     */
+    public boolean isOkSystemSettingPassword(String inpPass) {
+        return PASSWORD_SYSTEM_SETTINGS.equals(inpPass);
+    }
+    /**
+     * スリープ
+     * @param msec スリープ時間
+     */
+    public static void sleep(long msec){
+        try {
+            Thread.sleep(msec);
+        } catch (InterruptedException e) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー %s", e.toString()), EsnDebugLog.DIV.ERR);
+        }
+    }
+    /**
+     * WAVファイルの再生
+     * @param resId wavファイルのリソースID
+     * @param playTimeMsec wavファイルの再生時間
+     */
+    public void playWav(int resId, long playTimeMsec){
+        MediaPlayer mp = MediaPlayer.create(this, resId);
+        mp.start();
+        sleep(playTimeMsec);
+        mp.release();
+    }
+    //endregion
+}

+ 391 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/NoticeDialogFragment.java

@@ -0,0 +1,391 @@
+package jp.co.ecosysnetwork.ccloca_app;
+
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.InputType;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentActivity;
+
+import java.util.Calendar;
+
+/**
+ * ダイアログのホストにイベントを渡すダイアログ
+ */
+public class NoticeDialogFragment extends DialogFragment {
+    //region インタフェース
+    /**
+     * イベントコールバックリスナ
+     */
+    public interface NoticeDialogListener {
+        /**
+         * OKボタン押下コールバック
+         * @param dialog ダイアログ
+         */
+        public void onDialogPositiveClick(NoticeDialogFragment dialog);
+        /**
+         * キャンセルボタン押下コールバック
+         * @param dialog ダイアログ
+         */
+        public void onDialogNegativeClick(NoticeDialogFragment dialog);
+        /**
+         * ニュートラルボタン押下コールバック
+         * @param dialog ダイアログ
+         */
+        public void onDialogNeutralClick(NoticeDialogFragment dialog);
+    }
+    //endregion
+    //region メンバ変数
+    /**
+     * 自身のアクティビティ
+     */
+    private FragmentActivity _activity;
+    /**
+     * 自身のコンテキスト
+     */
+    private Context _context;
+    /**
+     * イベント通知リスナ
+     */
+    private NoticeDialogListener _listener;
+    /**
+     * ダイアログタイトル
+     */
+    private String _title;
+    /**
+     * ダイアログメッセージ
+     */
+    private String _message;
+    /**
+     * 編集用テキストを表示するか否か
+     */
+    private boolean _isShowEdit;
+    /**
+     * 編集用テキストに入力された文字を隠すか否か
+     */
+    private boolean _isEditPassword;
+    /**
+     * OKボタンテキスト
+     */
+    private String _posBtnText;
+    /**
+     * キャンセルボタンテキスト
+     */
+    private String _negBtnText;
+    /**
+     * ニュートラルボタンテキスト
+     */
+    private String _ntrBtnText;
+    /**
+     * 入力エディットテキスト
+     */
+    private EditText _edtInput;
+    /**
+     * 日付選択テキストを表示するか否か
+     */
+    private boolean _isShowDatePicker;
+    /**
+     * 日付選択エディットテキスト
+     */
+    private EditText _edtDate;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     * @param title ダイアログタイトル
+     * @param message ダイアログメッセージ
+     * @param isShowEdit 編集用テキストを表示するか否か
+     * @param isEditPassword 編集用テキストに入力された文字を隠すか否か
+     * @param posBtnText OKボタンテキスト
+     * @param negBtnText キャンセルボタンテキスト
+     */
+    public NoticeDialogFragment(String title, String message, boolean isShowEdit, boolean isEditPassword, String posBtnText, String negBtnText) {
+        _title = title;
+        _message = message;
+        _isShowEdit = isShowEdit;
+        _isEditPassword = isEditPassword;
+        _posBtnText = posBtnText;
+        _negBtnText = negBtnText;
+        _ntrBtnText = "";
+        _edtInput = null;
+        _isShowDatePicker = false;
+    }
+    /**
+     * コンストラクタ
+     * @param title ダイアログタイトル
+     * @param message ダイアログメッセージ
+     * @param isShowEdit 編集用テキストを表示するか否か
+     * @param isEditPassword 編集用テキストに入力された文字を隠すか否か
+     * @param posBtnText OKボタンテキスト
+     * @param negBtnText キャンセルボタンテキスト
+     * @param ntrBtnText ニュートラルボタンテキスト
+     */
+    public NoticeDialogFragment(String title, String message, boolean isShowEdit, boolean isEditPassword, String posBtnText, String negBtnText, String ntrBtnText) {
+        _title = title;
+        _message = message;
+        _isShowEdit = isShowEdit;
+        _isEditPassword = isEditPassword;
+        _posBtnText = posBtnText;
+        _negBtnText = negBtnText;
+        _ntrBtnText = ntrBtnText;
+        _edtInput = null;
+        _isShowDatePicker = false;
+    }
+    /**
+     * コンストラクタ
+     * @param title ダイアログタイトル
+     * @param message ダイアログメッセージ
+     * @param isShowEdit 編集用テキストを表示するか否か
+     * @param isEditPassword 編集用テキストに入力された文字を隠すか否か
+     * @param posBtnText OKボタンテキスト
+     * @param negBtnText キャンセルボタンテキスト
+     * @param ntrBtnText ニュートラルボタンテキスト
+     * @param isShowDatePicker 日付選択を可能にするか
+     */
+    public NoticeDialogFragment(String title, String message, boolean isShowEdit, boolean isEditPassword, String posBtnText, String negBtnText, String ntrBtnText, boolean isShowDatePicker) {
+        _title = title;
+        _message = message;
+        _isShowEdit = isShowEdit;
+        _isEditPassword = isEditPassword;
+        _posBtnText = posBtnText;
+        _negBtnText = negBtnText;
+        _ntrBtnText = ntrBtnText;
+        _edtInput = null;
+        _isShowDatePicker = isShowDatePicker;
+    }
+    //endregion
+    //region プロパティ
+    /**
+     * 入力エディットテキストのgetter
+     * @return 入力エディットテキスト
+     */
+    public EditText get_edtInput() {
+        return _edtInput;
+    }
+    /**
+     * 日付選択エディットテキストのgetter
+     * @return 日付選択エディットテキスト
+     */
+    public EditText get_edtDate() {return _edtDate;}
+    //endregion
+    //region イベント
+    /**
+     * アタッチイベント
+     * @param context ホストのコンテキスト
+     */
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        // コールバックリスナが登録されているかのチェック
+        try {
+            _activity = getActivity();
+            _context = getContext();
+            _listener = (NoticeDialogListener) context;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(context.toString() + " must implement NoticeDialogListener");
+        }
+    }
+    /**
+     * ダイアログ作成イベント
+     * @param savedInstanceState アクティビティが以前にシャットダウンされた後に再初期化されている場合、このバンドルにはonSaveInstanceState(Bundle)で最後に提供されたデータが含まれます。 注:それ以外の場合はnullです。
+     * @return ダイアログ
+     */
+    @Override
+    public @NonNull Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(_activity);
+
+        // ダイアログタイトル
+        if (!_title.equals("")) {
+            TextView titleView = new TextView(this.getContext());
+            titleView.setText(_title);
+            titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogTitleTextSize));
+            titleView.setTextColor(ContextCompat.getColor(_context, R.color.dialogTitleTextColor));
+            titleView.setBackgroundColor(ContextCompat.getColor(_context, R.color.dialogTitleColor));
+            int pad = (int)getResources().getDimension(R.dimen.dialogTitlePadding);
+            titleView.setPadding(pad, pad, pad, pad);
+            titleView.setGravity(Gravity.CENTER);
+            builder.setCustomTitle(titleView);
+        }
+
+        // ボディ部の垂直リニアレイアウト
+        LinearLayout lnlBody = null;
+        if (!_message.equals("") || _isShowEdit) {
+            lnlBody = new LinearLayout(this.getContext());
+            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT);
+            lnlBody.setLayoutParams(lp);
+            lnlBody.setOrientation(LinearLayout.VERTICAL);
+            int pad = (int)getResources().getDimension(R.dimen.dialogMsgPadding);
+            lnlBody.setPadding(pad, pad, pad, pad);
+        }
+
+        // ダイアログメッセージ
+        if (!_message.equals("")) {
+            TextView msgView = new TextView(this.getContext());
+            msgView.setText(_message);
+            msgView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogMsgTextSize));
+            msgView.setTextColor(ContextCompat.getColor(_context, R.color.dialogMsgTextColor));
+            msgView.setBackgroundColor(ContextCompat.getColor(_context, R.color.dialogMsgColor));
+            int pad = (int)getResources().getDimension(R.dimen.dialogMsgPadding);
+            msgView.setPadding(0, 0, 0, pad);
+            lnlBody.addView(msgView);
+        }
+
+        // 編集用テキスト
+        if (_isShowEdit) {
+            _edtInput = new EditText(this.getContext());
+            _edtInput.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogMsgTextSize));
+            _edtInput.setTextColor(ContextCompat.getColor(_context, R.color.editTextColor));
+            _edtInput.setBackground(_activity.getDrawable(R.drawable.bg_edittext));
+            int pad = (int)getResources().getDimension(R.dimen.dialogEditPadding);
+            _edtInput.setPadding(pad, pad, pad, pad);
+            if (_isEditPassword) {
+                _edtInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+            }
+            lnlBody.addView(_edtInput);
+        }
+
+        // 日付選択
+        if (_isShowDatePicker) {
+            _edtDate = new EditText(this.getContext());
+            _edtDate.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogMsgTextSize));
+//            _edtDate.setTextColor(ContextCompat.getColor(_context, R.color.editTextColor));
+//            _edtDate.setBackground(_activity.getDrawable(R.drawable.bg_edittext));
+            _edtDate.setFocusable(false);
+            int pad = (int)getResources().getDimension(R.dimen.dialogEditPadding);
+            _edtDate.setPadding(pad, pad, pad, pad);
+            _edtDate.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    //Calendarインスタンスを取得
+                    final Calendar date = Calendar.getInstance();
+                    //DatePickerDialogインスタンスを取得
+                    DatePickerDialog datePickerDialog = new DatePickerDialog(
+                            _context,
+                            new DatePickerDialog.OnDateSetListener() {
+                                @Override
+                                public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
+                                    //setした日付を取得して表示
+                                    _edtDate.setText(String.format("%d/%02d/%02d", year, month+1, dayOfMonth));
+                                }
+                            },
+                            date.get(Calendar.YEAR),
+                            date.get(Calendar.MONTH),
+                            date.get(Calendar.DATE)
+                    );
+                    //dialogを表示
+                    datePickerDialog.show();
+                }
+            });
+            lnlBody.addView(_edtDate);
+        }
+
+        // ボディ部の垂直リニアレイアウトを設定
+        if (lnlBody != null) {
+            builder.setView(lnlBody);
+        }
+
+        // OKボタン
+        if (!_posBtnText.equals("")) {
+            builder.setPositiveButton(_posBtnText, new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int id) {
+                    _listener.onDialogPositiveClick(NoticeDialogFragment.this);
+                }
+            });
+        }
+
+        // キャンセルボタン
+        if (!_negBtnText.equals("")) {
+            builder.setNegativeButton(_negBtnText, new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int id) {
+                    _listener.onDialogNegativeClick(NoticeDialogFragment.this);
+                }
+            });
+        }
+
+        // ニュートラルボタン
+        if (!_ntrBtnText.equals("")) {
+            builder.setNeutralButton(_ntrBtnText, new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int id) {
+                    _listener.onDialogNeutralClick(NoticeDialogFragment.this);
+                }
+            });
+        }
+
+        return builder.create();
+    }
+    /**
+     * アクティビティ開始イベント
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        AlertDialog ald = (AlertDialog)this.getDialog();
+        int margin = (int)getResources().getDimension(R.dimen.dialogBtnMargin);
+
+        // OKボタン
+        Button btnPos = ald.getButton(AlertDialog.BUTTON_POSITIVE);
+        btnPos.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogBtnTextSize));
+        btnPos.setTextColor(ContextCompat.getColor(_context, R.color.dialogBtnTextColor));
+        btnPos.setBackground(_activity.getDrawable(R.drawable.bg_button));
+        btnPos.setWidth((int)getResources().getDimensionPixelSize(R.dimen.dialogBtnWidth));
+        btnPos.setHeight((int)getResources().getDimensionPixelSize(R.dimen.dialogBtnHeight));
+
+        LinearLayout.LayoutParams lpPos = (LinearLayout.LayoutParams)btnPos.getLayoutParams();
+        lpPos.setMargins(0, 0, margin, 0);
+        btnPos.setLayoutParams(lpPos);
+
+        // キャンセルボタン
+        Button btnNeg = ald.getButton(AlertDialog.BUTTON_NEGATIVE);
+        btnNeg.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogBtnTextSize));
+        btnNeg.setTextColor(ContextCompat.getColor(_context, R.color.dialogBtnTextColor));
+        btnNeg.setBackground(_activity.getDrawable(R.drawable.bg_button));
+        btnNeg.setWidth((int)getResources().getDimensionPixelSize(R.dimen.dialogBtnWidth));
+        btnNeg.setHeight((int)getResources().getDimensionPixelSize(R.dimen.dialogBtnHeight));
+
+        LinearLayout.LayoutParams lpNeg = (LinearLayout.LayoutParams)btnNeg.getLayoutParams();
+        lpNeg.setMargins(0, 0, margin, 0);
+        btnNeg.setLayoutParams(lpNeg);
+
+        // ニュートラルボタン
+        Button btnNtr = ald.getButton(AlertDialog.BUTTON_NEUTRAL);
+        btnNtr.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.dialogBtnTextSize));
+        btnNtr.setTextColor(ContextCompat.getColor(_context, R.color.dialogBtnTextColor));
+        btnNtr.setBackground(_activity.getDrawable(R.drawable.bg_button));
+        btnNtr.setWidth((int)getResources().getDimensionPixelSize(R.dimen.dialogBtnWidth));
+        btnNtr.setHeight((int)getResources().getDimensionPixelSize(R.dimen.dialogBtnHeight));
+
+        LinearLayout.LayoutParams lpNtr = (LinearLayout.LayoutParams)btnNtr.getLayoutParams();
+        lpNtr.setMargins(margin, 0, 0, 0);
+        btnNtr.setLayoutParams(lpNtr);
+
+        // ボタンのマージンを設定するとダイアログサイズが変わらずはみ出すため、親ビューにパディングを設定
+        View viwParent = (View)btnPos.getParent();
+        viwParent.setPadding(0, margin, 0, margin);
+    }
+    /**
+     * 携帯端末ーがアクティビティを離れることを最初に示すイベント
+     */
+    @Override
+    public void onPause() {
+        super.onPause();
+        //dismiss();
+    }
+    //endregion
+}

+ 37 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/BaseActivity.java

@@ -0,0 +1,37 @@
+package jp.co.ecosysnetwork.ccloca_app.activity;
+
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * コース設定アクティビティ
+ */
+public class BaseActivity
+        extends AppCompatActivity {
+    /**
+     * 送信文字列の追加
+     * @param os データ出力ストリーム
+     * @param boundary 境界
+     * @param name 属性名
+     * @param value 属性値
+     * @throws IOException 入出力例外を返すことがある
+     */
+    protected void append_string(DataOutputStream os, String boundary, String name, String value) throws IOException {
+        os.writeBytes("--" + boundary + "\r\n");
+        os.writeBytes("Content-Disposition: form-data; name=" + "\"" + name + "\"" + "\r\n" + "\r\n");
+        os.writeBytes(value + "\r\n");
+    }
+    /**
+     * 送信データの終端設定
+     * @param os データ入出力ストリーム
+     * @param boundary 境界
+     * @throws IOException 入出力例外を返すことがある
+     */
+    protected void append_final(DataOutputStream os, String boundary) throws IOException {
+        os.writeBytes("--" + boundary + "--" + "\r\n");
+        os.close();
+    }
+}

+ 529 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/CourseSettingsActivity.java

@@ -0,0 +1,529 @@
+package jp.co.ecosysnetwork.ccloca_app.activity;
+
+import androidx.fragment.app.DialogFragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Xml;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.ccloca_app.NoticeDialogFragment;
+import jp.co.ecosysnetwork.ccloca_app.R;
+import jp.co.ecosysnetwork.ccloca_app.adapter.BusRouteAdapter;
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_info;
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_infos;
+import jp.co.ecosysnetwork.ccloca_app.models.course_settings;
+import jp.co.ecosysnetwork.ccloca_app.models.drive_state;
+import jp.co.ecosysnetwork.ccloca_app.models.system_settings;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+import jp.co.ecosysnetwork.universal.EsnUtility;
+import jp.co.ecosysnetwork.universal.EsnXmlParser;
+
+/**
+ * コース設定アクティビティ
+ */
+public class CourseSettingsActivity
+        extends BaseActivity
+        implements View.OnClickListener,
+        NoticeDialogFragment.NoticeDialogListener{
+    //region 定数
+    /**
+     * 保存確認ダイアログ
+     */
+    private static final String DIALOG_TAG_SAVE = "cfmSave";
+    /**
+     * コース更新確認ダイアログ
+     */
+    private static final String DIALOG_TAG_UPDATE_COURSE = "cfmUpdateCourse";
+    /**
+     * コース更新成功ダイアログ
+     */
+    private static final String DIALOG_TAG_UPDATE_COURSE_SUCCESS = "UpdateCourseSuccess";
+    /**
+     * コース更新成功の保存失敗ダイアログ
+     */
+    private static final String DIALOG_TAG_UPDATE_COURSE_SUCCESS_SAVE_ERROR = "UpdateCourseSuccessSaveError";
+    /**
+     * コース更新失敗ダイアログ
+     */
+    private static final String DIALOG_TAG_UPDATE_COURSE_ERROR = "UpdateCourseError";
+    /**
+     * コース更新要求ダイアログ
+     */
+    private static final String DIALOG_TAG_RESET_COURSE = "ResetCourse";
+    /**
+     * コース要求後の待機時間
+     */
+    private static final int WAIT_MSEC_REQ_COURSE_SEND = 1000;
+    //endregion
+    //region メンバ変数
+    /**
+     * シングルトンのアプリケーション参照
+     */
+    private final MyApplication _app = MyApplication.getInstance();
+    /**
+     * コース設定リストビュー
+     */
+    private Spinner _spnCourseSettings;
+    /**
+     * 保存ボタン
+     */
+    private Button _btnSave;
+    /**
+     * コース更新ボタン
+     */
+    private Button _btnUdpateCourse;
+    /**
+     * コース更新プログレスバー
+     */
+    private ProgressBar _prgUpdateCourse;
+    /**
+     * 保存元の値
+     */
+    private course_settings _org_model;
+    /**
+     * 編集中の値
+     */
+    private course_settings _tmp_model;
+    /**
+     * コース情報リストモデル
+     */
+    private busRoute_infos _busRoute_infos;
+    /**
+     * システム設定モデル
+     */
+    private system_settings _system_settings;
+    /**
+     * 通信中フラグ
+     */
+    private boolean _communicating;
+    //endregion
+    //region 内部メソッド
+    /**
+     * リフレッシュ(ボタン)
+     */
+    private void refreshButton() {
+        // 保存ボタン
+        _btnSave.setEnabled(!_communicating &&
+                !_org_model.equals(_tmp_model));
+
+        // コース更新ボタン
+        boolean enaFlg = !_communicating &&
+                (_system_settings.phone_id != null) &&
+                (_system_settings.phone_id.length() > 0);
+        _btnUdpateCourse.setEnabled(enaFlg);
+    }
+    /**
+     * 終了処理
+     */
+    private void terminate() {
+    }
+    /**
+     * 戻るボタン押下
+     */
+    private void clickBtnBack() {
+        EsnDebugLog.outputLog("戻るボタン押下", EsnDebugLog.DIV.INFO);
+
+        terminate();
+        finishAndRemoveTask();
+    }
+    /**
+     * 保存確認ダイアログ:はいボタン押下
+     */
+    private void clickPosCfmSave() {
+        // システム設定の保存処理
+        if (_tmp_model.savePreference()) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN,"コース設定の保存処理成功 %s", _tmp_model.toLogString()), EsnDebugLog.DIV.WARN);
+
+            // リフレッシュ
+            _org_model = _tmp_model.clone();
+            refreshButton();
+        }
+        else {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN,"コース設定の保存処理失敗 %s", _tmp_model.toLogString()), EsnDebugLog.DIV.ERR);
+        }
+    }
+    /**
+     *  保存ボタン押下
+     */
+    private void clickBtnSave() {
+        EsnDebugLog.outputLog("保存ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // 変更データがない場合は処理しない
+        if (_org_model.equals(_tmp_model)) {
+            EsnDebugLog.outputLog("変更データが存在しないため保存しない", EsnDebugLog.DIV.WARN);
+            return;
+        }
+
+        // 保存確認ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                getString(R.string.msgCourseSettingSave),
+                false,
+                false,
+                getString(R.string.yes),
+                getString(R.string.no)
+        );
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_SAVE);
+    }
+    /**
+     * コース更新確認ダイアログ:はいボタン押下
+     */
+    private void clickPosCfmUpdateCourse() {
+        // プログレスバー表示
+        _communicating = true;
+        refreshButton();
+        _prgUpdateCourse.setVisibility(View.VISIBLE);
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.execute(new Runnable() {
+            /**
+             * 携帯端末認証バックグラウンド処理
+             */
+            @Override
+            public void run() {
+                final String ret = updateCourseExec();
+                handler.postDelayed(new Runnable() {
+                    /**
+                     * バックグラウンド終了後のUI処理
+                     */
+                    @Override
+                    public void run() {
+                        // プログレスバー消す
+                        _communicating = false;
+                        _prgUpdateCourse.setVisibility(View.GONE);
+                        if (ret == null) {
+                            // コース更新成功
+                            if (_busRoute_infos.savePreference()) {
+                                EsnDebugLog.outputLog(String.format(Locale.JAPAN,"コース更新成功 %s", _busRoute_infos.toLogString()), EsnDebugLog.DIV.WARN);
+
+                                // 成功音声を再生
+                                _app.playWav(R.raw.tochaku, 500);
+
+                                // コース情報更新
+                                BusRouteAdapter adp2 = new BusRouteAdapter(_app.getApplicationContext(), (List<busRoute_info>)_busRoute_infos.items);
+                                _spnCourseSettings.setAdapter(adp2);
+
+                                SimpleDateFormat fmtYYYYMMDD = new SimpleDateFormat("yyyyMMdd", Locale.JAPANESE);
+                                String nowDate = fmtYYYYMMDD.format(new Date());
+                                EsnDebugLog.outputLog(String.format(Locale.JAPAN, "日替わり処理 %s -> %s", _app.drive_state_model.getLastUpdateCourseDate(), nowDate), EsnDebugLog.DIV.WARN);
+                                _app.drive_state_model.setLastUpdateCourseDate(nowDate, true);
+                                // コース更新成功ダイアログ
+                                DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                                        getString(R.string.msgSuccessUpdateCourse),
+                                        false,
+                                        false,
+                                        getString(R.string.ok),
+                                        "");
+                                newFragment.show(getSupportFragmentManager(), DIALOG_TAG_UPDATE_COURSE_SUCCESS);
+                            }
+                            else {
+                                EsnDebugLog.outputLog(String.format(Locale.JAPAN,"コース情報の保存処理失敗 %s", _busRoute_infos.toLogString()), EsnDebugLog.DIV.ERR);
+
+                                // 認証成功後の携帯端末ID保存失敗ダイアログ
+                                DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                                        getString(R.string.msgSuccessUpdateCourseButSaveError),
+                                        false,
+                                        false,
+                                        getString(R.string.ok),
+                                        "");
+                                newFragment.show(getSupportFragmentManager(), DIALOG_TAG_UPDATE_COURSE_SUCCESS_SAVE_ERROR);
+                            }
+                        }
+                        else {
+                            // 認証失敗ダイアログ
+                            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                                    ret,
+                                    false,
+                                    false,
+                                    getString(R.string.ok),
+                                    "");
+                            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_UPDATE_COURSE_ERROR);
+                        }
+                        // リフレッシュ
+                        refreshButton();
+                    }
+                }, 500);
+            }
+        });
+    }
+    /**
+     *  コース更新ボタン押下
+     */
+    private void clickBtnUpdateCourse() {
+        EsnDebugLog.outputLog("コース更新ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // コース更新確認ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                getString(R.string.msgUpdateCourse),
+                false,
+                false,
+                getString(R.string.yes),
+                getString(R.string.no)
+        );
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_UPDATE_COURSE);
+    }
+    //region コース更新処理
+    /**
+     * 携帯端末認証実行処理
+     * @return エラーメッセージ
+     */
+    private String updateCourseExec(){
+        String ret = null;
+
+        HttpURLConnection con = null;
+        DataOutputStream os = null;
+        InputStream is = null;
+        try {
+            URL accUrl = new URL(_system_settings.server_url + MyApplication.APIURL_GET_COURSE);
+            con = (HttpURLConnection)accUrl.openConnection();
+            con.setConnectTimeout(10 * 1000);
+            con.setRequestMethod("POST");
+            con.setDoOutput(true);
+            con.setDoInput(true);
+            con.setUseCaches(false);
+
+            // ヘッダ設定
+            con.setRequestProperty("ANDROID-API-KEY", MyApplication.API_KEY);
+            con.setRequestProperty("Connection", "Keep-Alive");
+            con.setRequestProperty("Charset", "UTF-8");
+            String boundary = "-----------------------------" + System.currentTimeMillis();
+            con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+            con.connect();
+
+            // 送信データの設定
+            os = new DataOutputStream(con.getOutputStream());
+            append_string(os, boundary, "intFacilityPhoneNo", _system_settings.phone_id);
+            append_final(os, boundary);
+            os = null;
+            // 携帯端末認証送信後のウェイト
+            MyApplication.sleep(WAIT_MSEC_REQ_COURSE_SEND);
+
+            // 受信データを取得
+            is = con.getInputStream();
+            // 受信データのXMLパース
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(is, "UTF-8");
+
+            // 受信データのパース
+            EsnXmlParser esnParser = new EsnXmlParser(parser);
+            ret = esnParser.tryParse();
+            if (ret == null) {
+                // パース成功
+                busRoute_info tmpBusRouteInfo = null;
+                List<busRoute_info> tmpBusRouteInfos = new ArrayList<>();
+                busRoute_info info = new busRoute_info();
+                info.bus_route_no = "0";
+                info.bus_route_name = "コースを選択してください";
+                tmpBusRouteInfos.add(info);
+                for (EsnXmlParser.ParseItem item : esnParser.getParseItems()) {
+                    if (item.tag.equals("intBusRouteNo")) {
+                        // コースのID
+                        if (!EsnUtility.isNumeric(item.value)){
+                            ret = String.format(Locale.JAPAN, "BusRouteNoが数値でない。 %s=%s", item.tag, item.value);
+                            EsnDebugLog.outputLog(ret, EsnDebugLog.DIV.ERR);
+                            break;
+                        }
+                        if (Integer.parseInt(item.value) < 0) {
+                            ret = String.format(Locale.JAPAN, "BusRouteNoの値が正常でない。 %s=%s", item.tag, item.value);
+                            EsnDebugLog.outputLog(ret, EsnDebugLog.DIV.ERR);
+                            break;
+                        }
+                        tmpBusRouteInfo = new busRoute_info();
+                        tmpBusRouteInfo.bus_route_no = item.value;
+                    }
+                    if (item.tag.equals("vcRouteName")) {
+                        // コースの名称
+                        if (tmpBusRouteInfo == null) {
+                            ret = String.format(Locale.JAPAN, "バス経路情報モデルが存在しません。 %s=%s", item.tag, item.value);
+                            EsnDebugLog.outputLog(ret, EsnDebugLog.DIV.ERR);
+                            break;
+                        }
+                        if (item.value.length() == 0) {
+                            ret = String.format(Locale.JAPAN, "BusRouteNameの値が正常でない。 %s=%s", item.tag, item.value);
+                            EsnDebugLog.outputLog(ret, EsnDebugLog.DIV.ERR);
+                            break;
+                        }
+                        tmpBusRouteInfo.bus_route_name = item.value;
+                    }
+                    if (tmpBusRouteInfo != null)
+                    {
+                        if (!tmpBusRouteInfo.bus_route_name.equals(""))
+                        {
+                            tmpBusRouteInfos.add(tmpBusRouteInfo);
+                            tmpBusRouteInfo = null;
+                        }
+                    }
+                }
+                if (ret == null) {
+                    // コース情報設定
+                    _busRoute_infos.items = tmpBusRouteInfos;
+                }
+            }
+        } catch (Exception e1) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー1 %s", e1.toString()), EsnDebugLog.DIV.ERR);
+            ret = getString(R.string.msgCommunicationError);
+        } finally {
+            try {
+                if (is != null) is.close();
+                if (os != null) os.close();
+                if (con != null) con.disconnect();
+            } catch (Exception e2) {
+                EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー2 %s", e2.toString()), EsnDebugLog.DIV.ERR);
+                ret = getString(R.string.msgCommunicateCmpltError);
+            }
+        }
+        return ret;
+    }
+    //endregion
+    //endregion
+    //region イベントメソッド
+    /**
+     * アクティビティ作成イベント
+     *
+     * @param savedInstanceState アクティビティが以前にシャットダウンされた後に再初期化されている場合、このバンドルにはonSaveInstanceState(Bundle)で最後に提供されたデータが含まれます。 注:それ以外の場合はnullです。
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_course_settings);
+        _app.drive_state_model.init();
+        // メンバ変数設定
+        _spnCourseSettings = findViewById(R.id.spnCourseSelect);
+        _btnSave = findViewById(R.id.btnSave);
+        _btnUdpateCourse = findViewById(R.id.btnUpdateCourse);
+        _prgUpdateCourse = findViewById(R.id.prgUpdateCourse);
+        _org_model = new course_settings();
+        _tmp_model = _org_model.clone();
+        _busRoute_infos = new busRoute_infos();
+        _system_settings = new system_settings();
+        _communicating = false;
+
+        // ボタンのリスナ登録
+        findViewById(R.id.imbBack).setOnClickListener(this);
+        _btnSave.setOnClickListener(this);
+        _btnUdpateCourse.setOnClickListener(this);
+        // 日付が変わったときにコースを更新するよう要求
+        SimpleDateFormat fmtYYYYMMDD = new SimpleDateFormat("yyyyMMdd", Locale.JAPANESE);
+        String nowDate = fmtYYYYMMDD.format(new Date());
+        if ((_app.drive_state_model.getLastUpdateCourseDate() == null) || (_app.drive_state_model.getLastUpdateCourseDate().compareTo(nowDate) < 0)) {
+            _org_model.initSelectedCourse();
+            _tmp_model = _org_model.clone();
+            _busRoute_infos.initBusRouteInfos();
+            // コース更新要求ダイアログ
+            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                    getString(R.string.msgResetCourse),
+                    false,
+                    false,
+                    getString(R.string.ok),
+                    "");
+            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_RESET_COURSE);
+        }
+        // コース選択スピナーを設定
+        BusRouteAdapter adp2 = new BusRouteAdapter(this, (List<busRoute_info>)_busRoute_infos.items);
+        _spnCourseSettings.setAdapter(adp2);
+        _spnCourseSettings.setSelection(_tmp_model.selected_position);
+        _spnCourseSettings.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
+            @Override
+            public void onItemSelected(AdapterView<?> parent,  View view,  int position,  long id) {
+                // 選択時の処理
+                _tmp_model.selected_busRoute_info = (busRoute_info) parent.getSelectedItem();
+                _tmp_model.selected_position = position;
+                _tmp_model.savePreference();
+                refreshButton();
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+                // 項目が撰択されなかった場合の処理
+            }
+        }) ;
+        // ボタンリフレッシュ
+        refreshButton();
+    }
+    /**
+     * クリックリスナー
+     * @param view 対象のビュー
+     */
+    public void onClick(View view) {
+        Intent intent;
+        switch (view.getId()) {
+            case R.id.imbBack: clickBtnBack(); break;   // 戻る
+            case R.id.btnSave: clickBtnSave(); break; // 保存ボタン
+            case R.id.btnUpdateCourse: clickBtnUpdateCourse(); break; // コース更新ボタン
+        }
+    }
+    /**
+     * ダイアログのOKボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogPositiveClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "POSSTIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+
+        switch (tag) {
+            case DIALOG_TAG_SAVE:
+                // 保存確認ダイアログではいボタン押下
+                clickPosCfmSave();
+                break;
+            case DIALOG_TAG_UPDATE_COURSE:
+                // コース更新確認ダイアログではいボタン押下
+                clickPosCfmUpdateCourse();
+                break;
+        }
+    }
+    /**
+     * ダイアログのキャンセルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNegativeClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEGATIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * ダイアログのニュートラルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNeutralClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEUTRALボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    //endregion
+}

+ 813 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/MainActivity.java

@@ -0,0 +1,813 @@
+package jp.co.ecosysnetwork.ccloca_app.activity;
+
+import androidx.fragment.app.DialogFragment;
+
+import android.annotation.SuppressLint;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.location.Location;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.util.Xml;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationCallback;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationResult;
+import com.google.android.gms.location.LocationServices;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.ccloca_app.NoticeDialogFragment;
+import jp.co.ecosysnetwork.ccloca_app.R;
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_info;
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_infos;
+import jp.co.ecosysnetwork.ccloca_app.models.course_settings;
+import jp.co.ecosysnetwork.ccloca_app.models.drive_state;
+import jp.co.ecosysnetwork.ccloca_app.models.system_settings;
+import jp.co.ecosysnetwork.ccloca_app.service.DriveService;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+import jp.co.ecosysnetwork.universal.EsnXmlParser;
+
+/**
+ * メインアクティビティ
+ */
+public class MainActivity
+        extends BaseActivity
+        implements View.OnClickListener,
+        NoticeDialogFragment.NoticeDialogListener {
+    //region 定数
+    /**
+     * 開始確認ダイアログ
+     */
+    private static final String DIALOG_TAG_START = "cfmStart";
+    /**
+     * 終了確認ダイアログ
+     */
+    private static final String DIALOG_TAG_EXIT = "cfmExit";
+    /**
+     * 運転条件エラーダイアログ
+     */
+    private static final String DIALOG_TAG_DRIVE_CONDITION_ERROR = "errDriveCondition";
+    /**
+     * コース終了失敗ダイアログ
+     */
+    private static final String DIALOG_TAG_END_COURSE_ERROR = "cfmEndCourseError";
+    /**
+     * コース開始成功ダイアログ
+     */
+    private static final String DIALOG_TAG_START_COURSE_ERROR = "StartCourseError";
+    /**
+     * コース更新要求ダイアログ
+     */
+    private static final String DIALOG_TAG_RESET_COURSE = "ResetCourse";
+    /**
+     * コース設定要求ダイアログ
+     */
+    private static final String DIALOG_TAG_SET_COURSE = "SetCourse";
+    /**
+     * 表示更新間隔
+     */
+    private static final int REFRESH_DIPLAY_CYCLE_MSEC = 5 * 1000;
+    /**
+     * サーバ通信サービスが停止していると判断する、サーバとの不通時間
+     */
+    private static final int DISABLE_MSEC_COMM = 30 * 1000;
+    /**
+     * 位置情報取得サービスが停止していると判断する、位置情報を取得していない時間
+     */
+    private static final int DISABLE_MSEC_LOLCATION = 30 * 1000;
+    /**
+     * コース開始,終了要求後の待機時間
+     */
+    private static final int WAIT_MSEC_REQ_COURSE_START_AND_END = 1000;
+    /**
+     * 位置情報を採用する最小精度
+     */
+    private static final float MIN_ACC = 100;
+    /**
+     * 初期位置情報取得サイクル
+     */
+    private static final long INIT_MSEC_LOCATION_INTERVAL = 1000;
+    /**
+     * 初期位置情報取得サイクル
+     */
+    private static final long INIT_MSEC_LOCATION_FAST_INTERVAL = 1000;
+    //endregion
+    //region メンバ変数
+    /**
+     * シングルトンのアプリケーション参照
+     */
+    private final MyApplication _app = MyApplication.getInstance();
+    /**
+     * ヘッダ部のリニアレイアウト
+     */
+    private LinearLayout _lnlHeader;
+    /**
+     * コース名テキストビュー
+     */
+    private TextView _txvCourseName;
+    /**
+     * 運行状況テキストビュー
+     */
+    private TextView _txvOperationStatus;
+    /**
+     * サーバ通信状態アイコン
+     */
+    private ImageView _imvCommState;
+    /**
+     * 位置情報取得状態アイコン
+     */
+    private ImageView _imvLocationState;
+    /**
+     * ボディ部のリニアレイアウト
+     */
+    private LinearLayout _lnlBody;
+    /**
+     * コース開始ボタン
+     */
+    private Button _btnStartDrive;
+    /**
+     * 電源管理
+     */
+    private PowerManager.WakeLock _wakeLock;
+    /**
+     * システム設定モデル
+     */
+    private system_settings _system_settings;
+    /**
+     * コース設定モデル
+     */
+    private course_settings _course_settings;
+    /**
+     * バス経路情報List
+     */
+    private busRoute_infos _busRoute_infos;
+    /**
+     * 表示更新ハンドラ
+     */
+    private Handler _refreshDisplayHandler;
+    /**
+     * 通信中フラグ
+     */
+    private boolean _communicating;
+    /**
+     * コース開始/終了プログレスバー
+     */
+    private ProgressBar _prgStartAndEndCourse;
+    /**
+     * 運行開始用位置情報コールバックハンドラ
+     */
+    private LocationCallback _startLocationCallback;
+    /**
+     * 運行終了用位置情報コールバックハンドラ
+     */
+    private LocationCallback _endLocationCallback;
+    /**
+     * 位置情報取得中であるか否か
+     */
+    private boolean _isActiveUpdateLocation = false;
+    /**
+     * 位置情報クライアント
+     */
+    private FusedLocationProviderClient _fusedLocationProviderClient;
+    //endregion
+    //region 内部メソッド
+    /**
+     * リフレッシュ(ボタン)
+     */
+    private void refreshButton() {
+        _btnStartDrive.setEnabled((!_course_settings.selected_busRoute_info.bus_route_no.equals("0")) &&
+                (!_communicating) &&
+                (!DriveService.isStarted()));
+    }
+    /**
+     * 終了処理
+     */
+    private void terminate() {
+        finishAndRemoveTask();
+    }
+
+    /**
+     * 開始ボタン押下処理
+     */
+    private void clickBtnStart() {
+        EsnDebugLog.outputLog("開始ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // 開始確認ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                getString(R.string.msgConfStartCourse),
+                false,
+                false,
+                getString(R.string.yes),
+                getString(R.string.no)
+        );
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_START);
+    }
+    /**
+     * 終了確認ダイアログ:はいボタン押下
+     */
+    private void clickPosCfmExit() {
+        // プログレスバー表示
+        _communicating = true;
+        _prgStartAndEndCourse.setVisibility(View.VISIBLE);
+        if (DriveService.isStarted()) {
+            startLocationUpdatesForCourseEnd(_endLocationCallback);
+        }
+        else {
+            terminate();
+        }
+    }
+    /**
+     * 運行終了呼出処理
+     */
+    private void callEndExec(final double lat, final double lon)
+    {
+        final Handler handler = new Handler(Looper.getMainLooper());
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.execute(new Runnable() {
+            /**
+             * 運行終了処理バックグラウンド処理
+             */
+            @Override
+            public void run() {
+                final String ret = startAndEndDriveExec(MyApplication.APIURL_END_BUS_COURSE, lat, lon);
+                handler.postDelayed(new Runnable() {
+                    /**
+                     * バックグラウンド終了後のUI処理
+                     */
+                    @Override
+                    public void run() {
+                        _communicating = false;
+                        if (ret == null) {
+                            _app.drive_state_model.setOperationState(drive_state.OPERATION_STATE.INACTIVE, true);
+                            terminate();
+                        } else {
+                            // 認証失敗ダイアログ
+                            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                                    ret,
+                                    false,
+                                    false,
+                                    getString(R.string.ok),
+                                    "");
+                            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_END_COURSE_ERROR);
+                        }
+                        // プログレスバー非表示
+                        _prgStartAndEndCourse.setVisibility(View.GONE);
+                    }
+                }, 500);
+            }
+        });
+    }
+    /**
+     * 終了ボタン押下処理
+     */
+    private void clickBtnExit() {
+        EsnDebugLog.outputLog("終了ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // 終了確認ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                getString(R.string.msgAppExist),
+                false,
+                false,
+                getString(R.string.yes),
+                getString(R.string.no)
+        );
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_EXIT);
+    }
+    /**
+     * 開始確認ダイアログ:はいボタン押下
+     */
+    private void clickPosCfmStart() {
+        // プログレスバー表示
+        _communicating = true;
+        _prgStartAndEndCourse.setVisibility(View.VISIBLE);
+        startLocationUpdates(_startLocationCallback);
+    }
+    /**
+     * 運行スタート呼出処理
+     */
+    private void callStartExec(final double lat, final double lon)
+    {
+        // 運転状態の初期化処理
+        _app.drive_state_model.init();
+        if(_course_settings.selected_busRoute_info.bus_route_no.equals("0"))
+        {
+            // コース設定要求ダイアログ
+            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                    getString(R.string.msgSetCourse),
+                    false,
+                    false,
+                    getString(R.string.ok),
+                    "");
+            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_SET_COURSE);
+            return;
+        }
+        // 日替わり時、運行状態をクリアする
+        SimpleDateFormat fmtYYYYMMDD = new SimpleDateFormat("yyyyMMdd", Locale.JAPANESE);
+        String nowDate = fmtYYYYMMDD.format(new Date());
+        if ((_app.drive_state_model.getLastUpdateCourseDate() == null) || (_app.drive_state_model.getLastUpdateCourseDate().compareTo(nowDate) < 0)) {
+            _course_settings.initSelectedCourse();
+            _busRoute_infos.initBusRouteInfos();
+            _app.drive_state_model.setOperationState(drive_state.OPERATION_STATE.INACTIVE, false);
+            // コース更新要求ダイアログ
+            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                    getString(R.string.msgResetCourse),
+                    false,
+                    false,
+                    getString(R.string.ok),
+                    "");
+            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_RESET_COURSE);
+            return;
+        }
+        final Handler handler = new Handler(Looper.getMainLooper());
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.execute(new Runnable() {
+            /**
+             * 運行開始処理バックグラウンド処理
+             */
+            @Override
+            public void run() {
+                final String ret = startAndEndDriveExec(MyApplication.APIURL_START_BUS_COURSE, lat, lon);
+                handler.postDelayed(new Runnable() {
+                    /**
+                     * バックグラウンド終了後のUI処理
+                     */
+                    @Override
+                    public void run() {
+                        _communicating = false;
+                        if (ret == null) {
+                            _app.drive_state_model.setOperationState(drive_state.OPERATION_STATE.ACTIVE, false);
+                            EsnDebugLog.outputLog("正常に運行を開始しました", EsnDebugLog.DIV.WARN);
+                            // エラーがない場合、運転制御開始
+                            initExec();
+                        }
+                        else {
+                            EsnDebugLog.outputLog(ret, EsnDebugLog.DIV.WARN);
+                            // 認証失敗ダイアログ
+                            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                                    ret,
+                                    false,
+                                    false,
+                                    getString(R.string.ok),
+                                    "");
+                            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_START_COURSE_ERROR);
+                        }
+                        // プログレスバー非表示
+                        _prgStartAndEndCourse.setVisibility(View.GONE);
+                        // リフレッシュ
+                        refreshButton();
+                    }
+                }, 500);
+            }
+        });
+    }
+
+    /**
+     * 運行開始,終了実行処理
+     * @return エラーメッセージ
+     */
+    private String startAndEndDriveExec(String url, double lat, double lon){
+        String ret = null;
+        // 日替わり時、運行状態をクリアする
+        SimpleDateFormat fmtYYYYMMDD = new SimpleDateFormat("yyyy-MM-dd", Locale.JAPANESE);
+        String nowDate = fmtYYYYMMDD.format(new Date());
+        SimpleDateFormat fmtHHiiss = new SimpleDateFormat("HH:mm:ss", Locale.JAPANESE);
+        String nowTime = fmtHHiiss.format(new Date());
+
+        HttpURLConnection con = null;
+        DataOutputStream os = null;
+        InputStream is = null;
+        try {
+            URL accUrl = new URL(_system_settings.server_url + url);
+            con = (HttpURLConnection)accUrl.openConnection();
+            con.setConnectTimeout(10 * 1000);
+            con.setRequestMethod("POST");
+            con.setDoOutput(true);
+            con.setDoInput(true);
+            con.setUseCaches(false);
+
+            // ヘッダ設定
+            con.setRequestProperty("ANDROID-API-KEY", MyApplication.API_KEY);
+            con.setRequestProperty("Connection", "Keep-Alive");
+            con.setRequestProperty("Charset", "UTF-8");
+            String boundary = "-----------------------------" + System.currentTimeMillis();
+            con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+            con.connect();
+
+            // 送信データの設定
+            os = new DataOutputStream(con.getOutputStream());
+            append_string(os, boundary, "intFacilityPhoneNo", _system_settings.phone_id);
+            append_string(os, boundary, "intBusRouteNo", _course_settings.selected_busRoute_info.bus_route_no);
+            append_string(os, boundary, "decLatitude", BigDecimal.valueOf(lat).toPlainString());
+            append_string(os, boundary, "decLongitude", BigDecimal.valueOf(lon).toPlainString());
+            append_string(os, boundary, "dtDate", nowDate + nowTime);
+            append_final(os, boundary);
+            os = null;
+            // 携帯端末認証送信後のウェイト
+            MyApplication.sleep(WAIT_MSEC_REQ_COURSE_START_AND_END);
+
+            // 受信データを取得
+            is = con.getInputStream();
+            // 受信データのXMLパース
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(is, "UTF-8");
+
+            // 受信データのパース
+            EsnXmlParser esnParser = new EsnXmlParser(parser);
+            ret = esnParser.tryParse();
+            if (ret == null) {
+                // パース成功
+                for (EsnXmlParser.ParseItem item : esnParser.getParseItems()) {
+                    if (item.tag.equals("errMsg")) {
+                        ret = String.format(Locale.JAPAN, "開始処理要求が失敗しました。\n %s", item.value);
+                        EsnDebugLog.outputLog(ret, EsnDebugLog.DIV.ERR);
+                        break;
+                    }
+                }
+            }
+        } catch (Exception e1) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー1 %s", e1.toString()), EsnDebugLog.DIV.ERR);
+            ret = getString(R.string.msgCommunicationError);
+        } finally {
+            try {
+                if (is != null) is.close();
+                if (os != null) os.close();
+                if (con != null) con.disconnect();
+            } catch (Exception e2) {
+                EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー2 %s", e2.toString()), EsnDebugLog.DIV.ERR);
+                ret = getString(R.string.msgCommunicateCmpltError);
+            }
+        }
+        return ret;
+    }
+    /**
+     * 表示更新
+     */
+    private void refreshDisplay() {
+        EsnDebugLog.outputLog("表示更新", EsnDebugLog.DIV.INFO);
+        // リフレッシュ
+        refreshButton();
+        if (_course_settings.selected_busRoute_info != null) {
+            _txvCourseName.setText(_course_settings.selected_busRoute_info.bus_route_name);
+        }
+        // 運行状態を表示
+        switch (_app.drive_state_model.getOperationState()) {
+            case INACTIVE:
+                _lnlHeader.setBackgroundColor(getColor(R.color.operationStateNeutral));
+                _txvCourseName.setTextColor(getColor(R.color.operationStateNeutralText));
+                _lnlBody.setBackgroundColor(getColor(R.color.operationStateNeutral));
+                _txvOperationStatus.setText(getString(R.string.driveStateEnd));
+                _txvOperationStatus.setTextColor(getColor(R.color.operationStateActiveText));
+                break;
+            case ACTIVE:
+                _lnlHeader.setBackgroundColor(getColor(R.color.operationStateActive));
+                _txvCourseName.setTextColor(getColor(R.color.operationStateActiveText));
+                _lnlBody.setBackgroundColor(getColor(R.color.operationStateActive));
+                _txvOperationStatus.setText(getString(R.string.driveStateStart));
+                _txvOperationStatus.setTextColor(getColor(R.color.approachColorArrial));
+                break;
+        }
+
+        // サーバとの通信状態を表示
+        if ((System.currentTimeMillis() - _app.drive_state_model.getLastCommActiveMsec()) > DISABLE_MSEC_COMM) {
+            //_imvCommState.setBackgroundResource(R.drawable.ic_comm_disable);
+            _imvCommState.setVisibility(View.INVISIBLE);
+        }
+        else {
+            _imvCommState.setBackgroundResource(R.drawable.ic_comm_enable);
+            _imvCommState.setVisibility(View.VISIBLE);
+        }
+
+        // 位置情報取得状態を表示
+        if ((System.currentTimeMillis() - _app.drive_state_model.getLastLocationActiveMsec()) > DISABLE_MSEC_LOLCATION) {
+            _imvLocationState.setBackgroundResource(R.drawable.ic_location_disable);
+        }
+        else {
+            _imvLocationState.setBackgroundResource(R.drawable.ic_location_enable);
+        }
+        _imvLocationState.setVisibility(View.VISIBLE);
+    }
+    /**
+     * 表示更新ハンドラ
+     */
+    private Runnable _refreshDisplayRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // 表示更新
+            refreshDisplay();
+
+            // タイマ指定
+            _refreshDisplayHandler.postDelayed(_refreshDisplayRunnable, REFRESH_DIPLAY_CYCLE_MSEC);
+        }
+    };
+    /**
+     * 位置情報取得の開始
+     */
+    @SuppressLint("MissingPermission")
+    private void startLocationUpdates(LocationCallback locationCallback) {
+        // 位置情報取得中の場合は一旦停止する
+        if (_isActiveUpdateLocation) {
+            stopLocationUpdates(locationCallback);
+        }
+        // 位置情報の取得を開始する
+        LocationRequest locReq = LocationRequest.create();
+        locReq.setInterval(MainActivity.INIT_MSEC_LOCATION_INTERVAL);
+        locReq.setFastestInterval(MainActivity.INIT_MSEC_LOCATION_FAST_INTERVAL);
+        locReq.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+        _fusedLocationProviderClient.requestLocationUpdates(locReq, locationCallback, Looper.getMainLooper());
+        _isActiveUpdateLocation = true;
+
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "位置情報の取得開始 interval=%d fastestInterval=%d", MainActivity.INIT_MSEC_LOCATION_INTERVAL, MainActivity.INIT_MSEC_LOCATION_FAST_INTERVAL), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * 位置情報取得の終了
+     */
+    @SuppressLint("MissingPermission")
+    private void startLocationUpdatesForCourseEnd(LocationCallback locationCallback) {
+        // 位置情報の取得を開始する
+        LocationRequest locReq = LocationRequest.create();
+        locReq.setInterval(MainActivity.INIT_MSEC_LOCATION_INTERVAL);
+        locReq.setFastestInterval(MainActivity.INIT_MSEC_LOCATION_FAST_INTERVAL);
+        locReq.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+        _fusedLocationProviderClient.requestLocationUpdates(locReq, locationCallback, Looper.getMainLooper());
+        _isActiveUpdateLocation = true;
+
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "位置情報の取得開始 interval=%d fastestInterval=%d", MainActivity.INIT_MSEC_LOCATION_INTERVAL, MainActivity.INIT_MSEC_LOCATION_FAST_INTERVAL), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * 位置情報取得の停止
+     */
+    private void stopLocationUpdates(LocationCallback locationCallback) {
+        if (_isActiveUpdateLocation) {
+            EsnDebugLog.outputLog("位置情報の更新を停止", EsnDebugLog.DIV.INFO);
+            _fusedLocationProviderClient.removeLocationUpdates(locationCallback);
+            _isActiveUpdateLocation = false;
+        }
+        else {
+            EsnDebugLog.outputLog("位置情報の更新は既に停止している", EsnDebugLog.DIV.WARN);
+        }
+    }
+    /**
+     * 初期化処理
+     */
+    private void initExec() {
+        if (!DriveService.isStarted()) {
+            // 運転サービスの通知
+            NotificationChannel channel = new NotificationChannel(DriveService.CHANNEL_ID, getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT);
+            channel.setDescription(getString(R.string.driveNotifyDescription));
+            NotificationManager manager = getSystemService(NotificationManager.class);
+            manager.createNotificationChannel(channel);
+
+            // 運転サービスの開始
+            Intent intent = new Intent(this, DriveService.class);
+            startForegroundService(intent);
+        }
+        else {
+            EsnDebugLog.outputLog("メイン画面 既に運転サービス起動済のため、起動しない", EsnDebugLog.DIV.INFO);
+        }
+
+        // 表示更新
+        refreshDisplay();
+
+        // 表示更新タイマ起動
+        _refreshDisplayHandler = new Handler(Looper.getMainLooper());
+        _refreshDisplayHandler.postDelayed(_refreshDisplayRunnable, REFRESH_DIPLAY_CYCLE_MSEC);
+    }
+    //endregion
+    //region イベントメソッド
+    /**
+     * アクティビティ作成イベント
+     *
+     * @param savedInstanceState アクティビティが以前にシャットダウンされた後に再初期化されている場合、このバンドルにはonSaveInstanceState(Bundle)で最後に提供されたデータが含まれます。 注:それ以外の場合はnullです。
+     */
+    @SuppressLint("WakelockTimeout")
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        EsnDebugLog.outputLog("メイン画面 onCreate", EsnDebugLog.DIV.INFO);
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        // メンバ変数設定
+        _lnlHeader = findViewById(R.id.lnlHeader);
+        _txvCourseName = findViewById(R.id.txvSelectedCourseName);
+        _txvOperationStatus = findViewById(R.id.txvOperationStatus);
+        _imvCommState = findViewById(R.id.imvCommState);
+        _imvLocationState = findViewById(R.id.imvLocationState);
+        _lnlBody = findViewById(R.id.lnlBody);
+        _btnStartDrive = findViewById(R.id.btnStartDrive);
+        _prgStartAndEndCourse = findViewById(R.id.prgStartAndEndCourse);
+        _system_settings = new system_settings();
+        _course_settings = new course_settings();
+        _busRoute_infos = new busRoute_infos();
+        // 位置情報クライアント生成
+        _fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
+
+        // ボタンのリスナ登録
+        findViewById(R.id.btnExit).setOnClickListener(this);
+        _btnStartDrive.setOnClickListener(this);
+
+        // ボタンリフレッシュ
+        refreshButton();
+        // 運転状態の初期化処理
+        _app.drive_state_model.init();
+
+        // CPUを常にONにする
+        _wakeLock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getPackageName());
+        _wakeLock.acquire();
+
+        // 携帯端末ID、コースが未設定の場合、エラー情報を表示し、プログラムを終了する
+        StringBuilder errMsg = new StringBuilder();
+        if ((_system_settings.phone_id == null) || (_system_settings.phone_id.length() == 0)) {
+            errMsg.append(getString(R.string.msgSetPhoneId)).append("\n");
+        }
+        if (_course_settings.selected_busRoute_info.bus_route_no.equals("0")) {
+            errMsg.append(getString(R.string.msgSetCourse)).append("\n");
+        }
+        if (errMsg.length() > 0) {
+            // 運転条件エラー
+            EsnDebugLog.outputLog(errMsg.toString(), EsnDebugLog.DIV.WARN);
+
+            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                    errMsg.toString(),
+                    false,
+                    false,
+                    getString(R.string.ok),
+                    "");
+            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_DRIVE_CONDITION_ERROR);
+        }
+        // ロケーションコールバック定義
+        _startLocationCallback = new LocationCallback() {
+            /**
+             * ロケーション取得結果
+             *
+             * @param locationResult 位置情報の取得結果
+             */
+            @Override
+            public void onLocationResult(LocationResult locationResult) {
+                stopLocationUpdates(_startLocationCallback);
+                if (locationResult != null) {
+                    for (Location loc : locationResult.getLocations()) {
+                        double lat = loc.getLatitude();     // 緯度
+                        double lon = loc.getLongitude();    // 経度
+                        float acc = loc.getAccuracy();      // 精度
+                        // 取得値が最低精度を満たしている場合、位置情報制御を実施する
+                        if ((acc < MIN_ACC) && (0 < lat) && (0 < lon)) {
+                            callStartExec(lat, lon);
+                        }
+                    }
+                }
+                // リフレッシュ
+                refreshButton();
+                super.onLocationResult(locationResult);
+            }
+        };
+        // ロケーションコールバック定義
+        _endLocationCallback = new LocationCallback() {
+            /**
+             * ロケーション取得結果
+             *
+             * @param locationResult 位置情報の取得結果
+             */
+            @Override
+            public void onLocationResult(LocationResult locationResult) {
+                stopLocationUpdates(_endLocationCallback);
+                if (locationResult != null) {
+                    for (Location loc : locationResult.getLocations()) {
+                        double lat = loc.getLatitude();     // 緯度
+                        double lon = loc.getLongitude();    // 経度
+                        float acc = loc.getAccuracy();      // 精度
+                        // 取得値が最低精度を満たしている場合、位置情報制御を実施する
+                        if ((acc < MIN_ACC) && (0 < lat) && (0 < lon)) {
+                            callEndExec(lat, lon);
+                        }
+                    }
+                }
+                super.onLocationResult(locationResult);
+            }
+        };
+        refreshDisplay();
+    }
+    /**
+     * アクティビティの開始
+     */
+    @Override
+    protected void onStart() {
+        EsnDebugLog.outputLog("メイン画面 onStart", EsnDebugLog.DIV.INFO);
+        super.onStart();
+    }
+    /**
+     * アクティビティの停止
+     */
+    @Override
+    protected void onStop() {
+        EsnDebugLog.outputLog("メイン画面 onStop", EsnDebugLog.DIV.INFO);
+        super.onStop();
+    }
+    /**
+     * アクティビティの終了
+     */
+    @Override
+    protected void onDestroy() {
+        EsnDebugLog.outputLog("メイン画面 onDestroy", EsnDebugLog.DIV.INFO);
+        super.onDestroy();
+        // 表示更新の解除
+        if (_refreshDisplayRunnable != null) {
+            _refreshDisplayHandler.removeCallbacks(_refreshDisplayRunnable);
+        }
+        // サービスの停止
+        Intent intent = new Intent(this, DriveService.class);
+        stopService(intent);
+        // CPUロックの解除
+        _wakeLock.release();
+    }
+    /**
+     * クリックリスナー
+     * @param view 対象のビュー
+     */
+    public void onClick(View view) {
+        Intent intent;
+        switch (view.getId()) {
+            case R.id.btnExit: clickBtnExit(); break;   // 終了ボタン
+            case R.id.btnStartDrive: clickBtnStart(); break;   // 終了ボタン
+        }
+    }
+    /**
+     * ダイアログのOKボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogPositiveClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "POSSTIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+
+        switch (tag) {
+            case DIALOG_TAG_EXIT:
+                // 終了確認ダイアログではいボタン押下
+                clickPosCfmExit();
+                break;
+            case DIALOG_TAG_DRIVE_CONDITION_ERROR:
+            case DIALOG_TAG_RESET_COURSE:
+            case DIALOG_TAG_SET_COURSE:
+                // 運転条件エラーでOKボタン押下
+                terminate();
+                break;
+            case DIALOG_TAG_START:
+                // 開始確認ダイアログではいボタン押下
+                clickPosCfmStart();
+                break;
+        }
+    }
+    /**
+     * ダイアログのキャンセルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNegativeClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEGATIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * ダイアログのニュートラルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNeutralClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEUTRALボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    //endregion
+}

+ 324 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/SplashActivity.java

@@ -0,0 +1,324 @@
+package jp.co.ecosysnetwork.ccloca_app.activity;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.fragment.app.DialogFragment;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.ccloca_app.NoticeDialogFragment;
+import jp.co.ecosysnetwork.ccloca_app.R;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+import jp.co.ecosysnetwork.universal.EsnUtility;
+
+/**
+ * スプラッシュアクティビティ
+ */
+public class SplashActivity
+        extends AppCompatActivity
+        implements View.OnClickListener,
+                   NoticeDialogFragment.NoticeDialogListener {
+    //region 定数
+    /**
+     * パーミッション要求用の識別コード
+     */
+    private static final int REQUEST_CODE = 200;
+    /**
+     * コース設定画面遷移時のパスワード入力ダイアログタグ
+     */
+    private static final String DIALOG_TAG_COURSE_SETTING_PASSWORD = "courseSettingPassword";
+    /**
+     * コース設定画面パスワード違いダイアログ
+     */
+    private static final String DIALOG_TAG_NEQ_COURSE_SETTING_PASSWORD = "neqCourseSettingPassword";
+    /**
+     * システム設定画面遷移時のパスワード入力ダイアログタグ
+     */
+    private static final String DIALOG_TAG_SYSTEM_SETTING_PASSWORD = "systemSettingPassword";
+    /**
+     * システム設定画面パスワード違いダイアログ
+     */
+    private static final String DIALOG_TAG_NEQ_SYSTEM_SETTING_PASSWORD = "neqSystemSettingPassword";
+    //endregion
+    //region メンバ変数
+    /**
+     * シングルトンのアプリケーション参照
+     */
+    private MyApplication _app = MyApplication.getInstance();
+    /**
+     * 自動スタートハンドラ
+     */
+    private Handler _autoStartHandler;
+    //endregion
+    //region 内部メソッド
+    /**
+     * 開始処理
+     */
+    private void startExec() {
+        // メインアクティビティに遷移
+        Intent intent = new Intent(getApplication(), MainActivity.class);
+        startActivity(intent);
+
+        // スプラッシュアクティビティは終了
+        finish();
+    }
+    /**
+     * 自動スタートハンドラ
+     */
+    private Runnable _autoStartRunnable = new Runnable() {
+        @Override
+        public void run() {
+            EsnDebugLog.outputLog("自動スタート", EsnDebugLog.DIV.INFO);
+            // 開始処理
+            startExec();
+        }
+    };
+    /**
+     * 初期処理
+     */
+    private void initExec() {
+        // ログ出力初期化
+        EsnDebugLog.init(_app.getLogDirectory(), "ccloca_app_.log", 10, EsnDebugLog.DIV.DBG);
+
+        // ログ出力
+        Context ctx = _app.getApplicationContext();
+        EsnDebugLog.outputLog("スプラッシュ起動 Ver." + EsnUtility.getVersionName(ctx), EsnDebugLog.DIV.INFO);
+
+        // 10秒後にメインアクティビティに遷移する
+        _autoStartHandler = new Handler(Looper.getMainLooper());
+        _autoStartHandler.postDelayed(_autoStartRunnable, 10 * 1000);
+    }
+    /**
+     * 開始ボタン押下処理
+     */
+    private void clickBtnStart() {
+        EsnDebugLog.outputLog("開始ボタン押下", EsnDebugLog.DIV.INFO);
+        // 開始処理
+        startExec();
+    }
+    /**
+     * コース設定パスワード入力ダイアログ-OKボタン押下処理
+     * @param inp 入力テキスト
+     */
+    private void clickPosCourseSettingPassword(String inp) {
+        if (_app.isOkCourseSettingPassword(inp)) {
+            // コース設定アクティビティに遷移
+            Intent intent = new Intent(getApplication(), CourseSettingsActivity.class);
+            startActivity(intent);
+        }
+        else {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "コース設定パスワード間違い %s", inp), EsnDebugLog.DIV.WARN);
+
+            // パスワードエラーダイアログを表示
+            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                    getString(R.string.msgNeqCourseSettingPassword),
+                    false,
+                    false,
+                    getString(R.string.ok),
+                    "");
+            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_NEQ_COURSE_SETTING_PASSWORD);
+        }
+    }
+    /**
+     * コース設定ボタン押下処理
+     */
+    private void clickBtnCourseSettings() {
+        EsnDebugLog.outputLog("コース設定ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // パスワード入力ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.courseSettings),
+                getString(R.string.inpPassword),
+                true,
+                true,
+                getString(R.string.ok),
+                getString(R.string.cancel));
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_COURSE_SETTING_PASSWORD);
+    }
+    /**
+     * システム設定パスワード入力ダイアログ-OKボタン押下処理
+     * @param inp 入力テキスト
+     */
+    private void clickPosSystemSettingPassword(String inp) {
+        if (_app.isOkSystemSettingPassword(inp)) {
+            // システム設定アクティビティに遷移
+            Intent intent = new Intent(getApplication(), SystemSettingsActivity.class);
+            startActivity(intent);
+        }
+        else {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "システム設定パスワード間違い %s", inp), EsnDebugLog.DIV.WARN);
+
+            // パスワードエラーダイアログを表示
+            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                    getString(R.string.msgNeqSystemSettingPassword),
+                    false,
+                    false,
+                    getString(R.string.ok),
+                    "");
+            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_NEQ_SYSTEM_SETTING_PASSWORD);
+        }
+    }
+    /**
+     * システム設定ボタン押下処理
+     */
+    private void clickBtnSystemSettings() {
+        EsnDebugLog.outputLog("システム設定ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // パスワード入力ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.systemSettings),
+                getString(R.string.inpPassword),
+                true,
+                true,
+                getString(R.string.ok),
+                getString(R.string.cancel));
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_SYSTEM_SETTING_PASSWORD);
+    }
+    //endregion
+    //region イベントメソッド
+    /**
+     * ダイアログのOKボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogPositiveClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "POSSTIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+
+        switch (tag) {
+            case DIALOG_TAG_COURSE_SETTING_PASSWORD:
+                // コース設定確認ダイアログではいボタン押下
+                clickPosCourseSettingPassword(dialog.get_edtInput().getText().toString());
+                break;
+            case DIALOG_TAG_SYSTEM_SETTING_PASSWORD:
+                // システム設定確認ダイアログではいボタン押下
+                clickPosSystemSettingPassword(dialog.get_edtInput().getText().toString());
+                break;
+        }
+    }
+    /**
+     * ダイアログのキャンセルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNegativeClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEGATIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * ダイアログのニュートラルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNeutralClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEUTRALボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * クリックリスナー
+     * @param view 対象のビュー
+     */
+    public void onClick(View view) {
+        // 自動スタートの解除
+        _autoStartHandler.removeCallbacks(_autoStartRunnable);
+
+        switch (view.getId()) {
+            case R.id.btnStart: clickBtnStart(); break;   // 開始ボタン
+            case R.id.btnCourseSettings: clickBtnCourseSettings(); break;   // コース設定ボタン
+            case R.id.btnSystemSettings: clickBtnSystemSettings(); break;   // システム設定ボタン
+        }
+    }
+    /**
+     * アクティビティ作成イベント
+     *
+     * @param savedInstanceState アクティビティが以前にシャットダウンされた後に再初期化されている場合、このバンドルにはonSaveInstanceState(Bundle)で最後に提供されたデータが含まれます。 注:それ以外の場合はnullです。
+     */
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_splash);
+
+        // ボタンのリスナ登録
+        findViewById(R.id.btnStart).setOnClickListener(this);
+        findViewById(R.id.btnCourseSettings).setOnClickListener(this);
+        findViewById(R.id.btnSystemSettings).setOnClickListener(this);
+
+        // バージョン番号表示
+        TextView txvVersion = findViewById(R.id.txvVersion);
+        Context ctx = _app.getApplicationContext();
+        String msg = getResources().getString(R.string.version) + EsnUtility.getVersionName(ctx);
+        txvVersion.setText(msg);
+
+        // パーミッションの付与
+        if ((ActivityCompat.checkSelfPermission(ctx, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
+                || (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED)
+                || (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
+                || (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
+                || (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED)
+        ) {
+            // パーミッションがない場合、パーミッションを要求
+            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                    Manifest.permission.INTERNET,
+                    Manifest.permission.ACCESS_FINE_LOCATION,
+                    Manifest.permission.ACCESS_COARSE_LOCATION,
+                    Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+            }, REQUEST_CODE);
+        } else {
+            // 初期処理
+            initExec();
+        }
+//            // 初期処理
+//            initExec();
+    }
+    /**
+     * パーミッション要求ダイアログで携帯端末が選択した際のイベント
+     *
+     * @param requestCode  要求コード
+     * @param permissions  パーミッションリスト
+     * @param grantResults 要求結果リスト
+     */
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        if (requestCode == REQUEST_CODE) {
+            if ((grantResults.length >= 5)
+                    && (grantResults[0] == PackageManager.PERMISSION_GRANTED)
+                    && (grantResults[1] == PackageManager.PERMISSION_GRANTED)
+                    && (grantResults[2] == PackageManager.PERMISSION_GRANTED)
+                    && (grantResults[3] == PackageManager.PERMISSION_GRANTED)
+                    && (grantResults[4] == PackageManager.PERMISSION_GRANTED)
+            ) {
+                // パーミッションが得られた場合は処理を継続
+                initExec();
+            } else {
+                // パーミッションが得られなかった場合は、プログラムを終了する
+                finishAndRemoveTask();
+            }
+        }
+    }
+    //endregion
+}

+ 526 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/activity/SystemSettingsActivity.java

@@ -0,0 +1,526 @@
+package jp.co.ecosysnetwork.ccloca_app.activity;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.DialogFragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Xml;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.ccloca_app.NoticeDialogFragment;
+import jp.co.ecosysnetwork.ccloca_app.R;
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_infos;
+import jp.co.ecosysnetwork.ccloca_app.models.course_settings;
+import jp.co.ecosysnetwork.ccloca_app.models.system_settings;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+import jp.co.ecosysnetwork.universal.EsnUtility;
+import jp.co.ecosysnetwork.universal.EsnXmlParser;
+
+/**
+ * システム設定アクティビティ
+ */
+public class SystemSettingsActivity
+        extends BaseActivity
+        implements View.OnClickListener,
+                    NoticeDialogFragment.NoticeDialogListener {
+    //region 定数
+    /**
+     * 保存確認ダイアログ
+     */
+    private static final String DIALOG_TAG_SAVE = "cfmSave";
+    /**
+     * 認証確認ダイアログ
+     */
+    private static final String DIALOG_TAG_AUTH = "cfmAuth";
+    /**
+     * 認証成功ダイアログ
+     */
+    private static final String DIALOG_TAG_AUTH_SUCCESS = "AuthSuccess";
+    /**
+     * 認証成功の保存失敗ダイアログ
+     */
+    private static final String DIALOG_TAG_AUTH_SUCCESS_SAVE_ERROR = "AuthSuccessSaveError";
+    /**
+     * 認証失敗ダイアログ
+     */
+    private static final String DIALOG_TAG_AUTH_ERROR = "AuthError";
+    /**
+     * 携帯端末認証送信後の待機時間
+     */
+    private static final int WAIT_MSEC_USER_AUTH_SEND = 1000;
+    //endregion
+    //region メンバ変数
+    /**
+     * サーバURLエディットテキスト
+     */
+    private EditText _edtServerUrl;
+    /**
+     * 更新間隔エディットテキスト
+     */
+    private EditText _edtUpdateCycleMsec;
+    /**
+     * システム設定保存ボタン
+     */
+    private Button _btnSave;
+    /**
+     * 携帯端末IDエディットテキスト
+     */
+    private EditText _edtPhoneId;
+    /**
+     * 携帯端末認証ボタン
+     */
+    private Button _btnAuth;
+    /**
+     * 携帯端末認証プログレスバー
+     */
+    private ProgressBar _prgAuth;
+    /**
+     * 保存元の値
+     */
+    private system_settings _org_model;
+    /**
+     * 編集中の値
+     */
+    private system_settings _tmp_model;
+    /**
+     * 通信中フラグ
+     */
+    private boolean _communicating;
+    /**
+     * コース設定モデル
+     */
+    private course_settings _course_settings;
+    /**
+     * バス経路情報List
+     */
+    private busRoute_infos _busRoute_infos;
+    //endregion
+    //region 内部メソッド
+    /**
+     * リフレッシュ(ビュー)
+     */
+    private void refreshView() {
+        // サーバURL
+        _edtServerUrl.setText(_tmp_model.server_url);
+
+        // 携帯端末ID
+        _edtPhoneId.setText(_tmp_model.phone_id);
+    }
+    /**
+     * リフレッシュ(ボタン)
+     */
+    private void refreshButton() {
+        // 保存ボタン
+        _btnSave.setEnabled(!_communicating &&
+                            !_org_model.equals(_tmp_model, system_settings.SAVE_ITEM.EXC_USER_ID));
+
+        // 認証ボタン
+        boolean isEnableAuth = !_communicating &&
+                                (!_org_model.equals(_tmp_model, system_settings.SAVE_ITEM.ONLY_PHONE_ID));
+        _btnAuth.setEnabled(isEnableAuth);
+    }
+    /**
+     * 終了処理
+     */
+    private void terminate() {
+    }
+    /**
+     * 戻るボタン押下
+     */
+    private void clickBtnBack() {
+        EsnDebugLog.outputLog("戻るボタン押下", EsnDebugLog.DIV.INFO);
+
+        terminate();
+        finishAndRemoveTask();
+    }
+    /**
+     * 保存確認ダイアログ:はいボタン押下
+     */
+    private void clickPosCfmSave() {
+        // システム設定の保存処理
+        if (_tmp_model.savePreference(system_settings.SAVE_ITEM.EXC_USER_ID)) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN,"システム設定の保存処理成功 %s", _tmp_model.toLogString()), EsnDebugLog.DIV.WARN);
+
+            // リフレッシュ
+            _tmp_model.copy(_org_model, system_settings.SAVE_ITEM.EXC_USER_ID);
+            refreshButton();
+        }
+        else {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN,"システム設定の保存処理失敗 %s", _tmp_model.toLogString()), EsnDebugLog.DIV.ERR);
+        }
+    }
+    /**
+     *  保存ボタン押下
+     */
+    private void clickBtnSave() {
+        EsnDebugLog.outputLog("保存ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // 変更データがない場合は処理しない
+        if (_org_model.equals(_tmp_model, system_settings.SAVE_ITEM.EXC_USER_ID)) {
+            EsnDebugLog.outputLog("変更データが存在しないため保存しない", EsnDebugLog.DIV.WARN);
+            return;
+        }
+
+        // 保存確認ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                getString(R.string.msgSystemSettingSave),
+                false,
+                false,
+                getString(R.string.yes),
+                getString(R.string.no)
+        );
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_SAVE);
+    }
+    /**
+     * 認証確認ダイアログ:はいボタン押下
+     */
+    private void clickPosCfmAuth() {
+        // プログレスバー表示
+        _communicating = true;
+        refreshButton();
+        _prgAuth.setVisibility(View.VISIBLE);
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.execute(new Runnable() {
+            /**
+             * 携帯端末認証バックグラウンド処理
+             */
+            @Override
+            public void run() {
+                final String ret = userAuthExec();
+                handler.postDelayed(new Runnable() {
+                    /**
+                     * バックグラウンド終了後のUI処理
+                     */
+                    @Override
+                    public void run() {
+                        // プログレスバー消す
+                        _communicating = false;
+                        _prgAuth.setVisibility(View.GONE);
+                        if (ret == null) {
+                            // 認証成功
+                            if (_tmp_model.savePreference(system_settings.SAVE_ITEM.ONLY_PHONE_ID)) {
+                                EsnDebugLog.outputLog(String.format(Locale.JAPAN,"携帯端末IDの認証処理成功 %s", _tmp_model.toLogString()), EsnDebugLog.DIV.WARN);
+
+                                // リフレッシュ
+                                _tmp_model.copy(_org_model, system_settings.SAVE_ITEM.ONLY_PHONE_ID);
+                                _course_settings.initSelectedCourse();
+                                _busRoute_infos.initBusRouteInfos();
+
+                                // 認証成功ダイアログ
+                                DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                                        getString(R.string.msgSuccessUserAuth),
+                                        false,
+                                        false,
+                                        getString(R.string.ok),
+                                        "");
+                                newFragment.show(getSupportFragmentManager(), DIALOG_TAG_AUTH_SUCCESS);
+                            }
+                            else {
+                                EsnDebugLog.outputLog(String.format(Locale.JAPAN,"携帯端末IDの保存処理失敗 %s", _tmp_model.toLogString()), EsnDebugLog.DIV.ERR);
+
+                                // 認証成功後の携帯端末ID保存失敗ダイアログ
+                                DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                                        getString(R.string.msgSuccessUserAuthButSaveError),
+                                        false,
+                                        false,
+                                        getString(R.string.ok),
+                                        "");
+                                newFragment.show(getSupportFragmentManager(), DIALOG_TAG_AUTH_SUCCESS_SAVE_ERROR);
+                            }
+                        }
+                        else {
+                            // 認証失敗ダイアログ
+                            DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleError),
+                                    ret,
+                                    false,
+                                    false,
+                                    getString(R.string.ok),
+                                    "");
+                            newFragment.show(getSupportFragmentManager(), DIALOG_TAG_AUTH_ERROR);
+                        }
+                        // リフレッシュ
+                        refreshView();
+                        refreshButton();
+                    }
+                }, 500);
+            }
+        });
+    }
+    /**
+     *  認証ボタン押下
+     */
+    private void clickBtnAuth() {
+        EsnDebugLog.outputLog("認証ボタン押下", EsnDebugLog.DIV.INFO);
+
+        // 変更データがない場合は処理しない
+        if (_org_model.equals(_tmp_model, system_settings.SAVE_ITEM.ONLY_PHONE_ID)) {
+            EsnDebugLog.outputLog("変更データが存在しないため認証しない", EsnDebugLog.DIV.WARN);
+            return;
+        }
+
+        // 認証確認ダイアログ表示
+        DialogFragment newFragment = new NoticeDialogFragment(getString(R.string.titleCfm),
+                getString(R.string.msgUserAuth),
+                false,
+                false,
+                getString(R.string.yes),
+                getString(R.string.no)
+        );
+        newFragment.show(getSupportFragmentManager(), DIALOG_TAG_AUTH);
+    }
+    //region 携帯端末認証処理
+    /**
+     * 携帯端末認証実行処理
+     * @return エラーメッセージ
+     */
+    private String userAuthExec(){
+        String ret = null;
+
+        HttpURLConnection con = null;
+        DataOutputStream os = null;
+        InputStream is = null;
+        try {
+            URL accUrl = new URL(_tmp_model.server_url + MyApplication.APIURL_CHECK_PHONE_ID);
+            con = (HttpURLConnection)accUrl.openConnection();
+            con.setRequestMethod("POST");
+            con.setDoOutput(true);
+            con.setDoInput(true);
+            con.setUseCaches(false);
+
+            // ヘッダ設定
+            con.setRequestProperty("ANDROID-API-KEY", MyApplication.API_KEY);
+            con.setRequestProperty("Connection", "Keep-Alive");
+            con.setRequestProperty("Charset", "UTF-8");
+            String boundary = "-----------------------------" + System.currentTimeMillis();
+            con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+            con.connect();
+
+            // 送信データの設定
+            os = new DataOutputStream(con.getOutputStream());
+            append_string(os, boundary, "intFacilityPhoneNo", _tmp_model.phone_id);
+            append_final(os, boundary);
+            os = null;
+
+            // 携帯端末認証送信後のウェイト
+            MyApplication.sleep(WAIT_MSEC_USER_AUTH_SEND);
+
+            // 受信データを取得
+            is = con.getInputStream();
+
+            // 受信データのXMLパース
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(is, "UTF-8");
+
+            // 受信データのパース
+            EsnXmlParser esnParser = new EsnXmlParser(parser);
+            ret = esnParser.tryParse();
+            if (ret == null) {
+                // パース成功
+                ret = getErrorMessage("4");   // 携帯端末ID不明
+                for (EsnXmlParser.ParseItem item : esnParser.getParseItems()) {
+                    if (item.tag.equals("intFacilityPhoneNo")) {
+                        // 携帯端末IDを更新
+                        _tmp_model.phone_id = item.value;
+                        ret = null;      // 認証成功
+                    }
+                    else if (item.tag.equals("intUpdateInterval")) {
+                        // 更新頻度
+                        _tmp_model.update_cycle_msec = Integer.parseInt(item.value);
+                    }
+                    else if (item.tag.equals("errorMsg")) {
+                        // エラーメッセージ設定
+                        ret = item.value;
+                    }
+                }
+            }else
+            {
+                ret = "データの変換に失敗しました。";
+            }
+        } catch (Exception e1) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー1 %s", e1.toString()), EsnDebugLog.DIV.ERR);
+            ret = getString(R.string.msgCommunicationError);
+        } finally {
+            try {
+                if (is != null) is.close();
+                if (os != null) os.close();
+                if (con != null) con.disconnect();
+            } catch (Exception e2) {
+                EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー2 %s", e2.toString()), EsnDebugLog.DIV.ERR);
+                ret = getString(R.string.msgCommunicateCmpltError);
+            }
+        }
+        return ret;
+    }
+    /**
+     * エラーメッセージの取得
+     * @param codeValue codeタグの値
+     * @return エラーメッセージ
+     */
+    private String getErrorMessage(String codeValue) {
+        String ret = null;
+        int tmpValue = -1;
+        if (EsnUtility.isNumeric(codeValue)) {
+            tmpValue = Integer.parseInt(codeValue);
+        }
+        switch (tmpValue) {
+            case 0: break;  // 正常
+            case 1: ret = getString(R.string.commErrmsg01); break;
+            case 2: ret = getString(R.string.commErrmsg02); break;
+            case 3: ret = getString(R.string.commErrmsg03); break;
+            case 4: ret = getString(R.string.commErrmsg04); break;
+            case 5: ret = getString(R.string.commErrmsg05); break;
+            case 6: ret = getString(R.string.commErrmsg06); break;
+            case 7: ret = getString(R.string.commErrmsg07); break;
+            case 8: ret = getString(R.string.commErrmsg08); break;
+            case 9: ret = getString(R.string.commErrmsg09); break;
+            case 10: ret = getString(R.string.commErrmsg10); break;
+            case 11: ret = getString(R.string.commErrmsg11); break;
+            case 12: ret = getString(R.string.commErrmsg12); break;
+            case 13: ret = getString(R.string.commErrmsg13); break;
+            case 14: ret = getString(R.string.commErrmsg14); break;
+            case 15: ret = getString(R.string.commErrmsg15); break;
+            default: ret = getString(R.string.commErrmsgOT); break;
+        }
+        return ret;
+    }
+    //endregion
+    //endregion
+    //region イベントメソッド
+    /**
+     * アクティビティ作成イベント
+     *
+     * @param savedInstanceState アクティビティが以前にシャットダウンされた後に再初期化されている場合、このバンドルにはonSaveInstanceState(Bundle)で最後に提供されたデータが含まれます。 注:それ以外の場合はnullです。
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_system_settings);
+
+        // メンバ変数設定
+        _edtServerUrl = findViewById(R.id.edtServerUrl);
+        _btnSave = findViewById(R.id.btnSave);
+        _edtPhoneId = findViewById(R.id.edtUserId);
+        _btnAuth = findViewById(R.id.btnAuth);
+        _prgAuth = findViewById(R.id.prgAuth);
+        _org_model = new system_settings();
+        _tmp_model = _org_model.clone();
+        _course_settings = new course_settings();
+        _busRoute_infos = new busRoute_infos();
+        _communicating = false;
+
+        // ボタンのリスナ登録
+        findViewById(R.id.imbBack).setOnClickListener(this);
+        _btnSave.setOnClickListener(this);
+        _btnAuth.setOnClickListener(this);
+
+        // サーバURLエディットテキストのリスナ登録
+        _edtServerUrl.addTextChangedListener(new TextWatcher() {
+            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { /*テキスト変更前*/ }
+            @Override public void onTextChanged(CharSequence s, int start, int before, int count) { /* テキスト変更中 */ }
+            @Override public void afterTextChanged(Editable s) { /* テキスト変更後 */
+                _tmp_model.server_url = s.toString();
+                refreshButton();
+            }
+        });
+
+        // 携帯端末IDエディットテキストのリスナ登録
+        _edtPhoneId.addTextChangedListener(new TextWatcher() {
+            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { /*テキスト変更前*/ }
+            @Override public void onTextChanged(CharSequence s, int start, int before, int count) { /* テキスト変更中 */ }
+            @Override public void afterTextChanged(Editable s) { /* テキスト変更後 */
+                _tmp_model.phone_id = s.toString();
+                refreshButton();
+            }
+        });
+
+        // リフレッシュ
+        refreshView();
+        refreshButton();
+    }
+    /**
+     * クリックリスナー
+     * @param view 対象のビュー
+     */
+    public void onClick(View view) {
+        Intent intent;
+        switch (view.getId()) {
+            case R.id.imbBack: clickBtnBack(); break;   // 戻る
+            case R.id.btnSave: clickBtnSave(); break; // 保存ボタン
+            case R.id.btnAuth: clickBtnAuth(); break; // 認証ボタン
+        }
+    }
+    /**
+     * ダイアログのOKボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogPositiveClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "POSSTIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+
+        switch (tag) {
+            case DIALOG_TAG_SAVE:
+                // 保存確認ダイアログではいボタン押下
+                clickPosCfmSave();
+                break;
+            case DIALOG_TAG_AUTH:
+                // 認証確認ダイアログではいボタン押下
+                clickPosCfmAuth();
+                break;
+        }
+    }
+    /**
+     * ダイアログのキャンセルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNegativeClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEGATIVEボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * ダイアログのニュートラルボタン押下イベント
+     * @param dialog ダイアログ
+     */
+    @Override
+    public void onDialogNeutralClick(NoticeDialogFragment dialog) {
+        String tag = dialog.getTag();
+        if (tag == null) {
+            EsnDebugLog.outputLog("ダイアログタグが設定されていない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "NEUTRALボタン押下 %s", tag), EsnDebugLog.DIV.INFO);
+    }
+    //endregion
+}

+ 112 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/adapter/BusRouteAdapter.java

@@ -0,0 +1,112 @@
+package jp.co.ecosysnetwork.ccloca_app.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_info;
+
+public class BusRouteAdapter extends ArrayAdapter<busRoute_info>
+{
+    //region メンバ変数
+    /**
+     * バスコース情報リスト
+     */
+    private List<busRoute_info> _items;
+    /**
+     * 選択コース
+     */
+    private int _selected_key;
+    /**
+     * 自身
+     */
+    private BusRouteAdapter _self;
+    /**
+     * コンテキスト
+     */
+    private Context _context;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     *
+     * @param context   コンテキスト
+     * @param items     リストビューの要素
+     */
+    public BusRouteAdapter(Context context, List<busRoute_info> items)
+    {
+        super(context,0, items);
+        _context = context;
+        _items = items;
+        _selected_key = 0;
+        _self = this;
+    }
+    //endregion
+    //region    イベントメソッド
+    @NonNull
+    @Override
+    public View getView(final int position, @Nullable View convertView,  @NonNull final ViewGroup parent)
+    {
+        TextView textView = new TextView(_context);
+        textView.setTextSize(25);
+        busRoute_info data = _items.get(position);
+        if (data != null) {
+            textView.setText(data.bus_route_name);
+        }
+        return textView;
+    }
+    @NonNull
+    @Override
+    public View getDropDownView(int position, View convertView, @NonNull final ViewGroup parent) {
+        TextView textView = new TextView(_context);
+        textView.setTextSize(25);
+        if (position <= this.getCount()) {
+            busRoute_info data = _items.get(position);
+            if (data != null) {
+                textView.setText(data.bus_route_name);
+            }
+        }
+        return textView;
+    }
+    /**
+     * 要素数取得
+     */
+    @Override
+    public int getCount() {
+        return _items.size();
+    }
+    /**
+     * 選択キーを設定
+     * @param info_id コースキー
+     */
+    public void setSelectedKey(int info_id) {
+        _selected_key = info_id;
+        // 描画更新
+        _self.notifyDataSetChanged();
+    }
+    /**
+     * 選択キーを取得
+     * @return コースキー
+     */
+    public int getSelectedKey() {
+        return _selected_key;
+    }
+    /**
+     * コース情報リストの設定
+     * @param items コース情報リスト
+     */
+    public void setItems(List<busRoute_info> items) {
+        _items = items;
+        // 描画更新
+        notifyDataSetChanged();
+    }
+    //endregion
+}

+ 81 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/busRoute_info.java

@@ -0,0 +1,81 @@
+package jp.co.ecosysnetwork.ccloca_app.models;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+public class busRoute_info implements Cloneable{
+
+    /**
+     *  コースのID
+     */
+    public String bus_route_no;
+    /**
+     * コース名称
+     */
+    public String bus_route_name;
+    //region 生成と廃棄
+    /*
+     *  コンストラクタ
+     */
+    public busRoute_info()
+    {
+        bus_route_no = "0";
+        bus_route_name = "";
+    }
+    //endregion
+
+    //region 外部公開メソッド
+    /**
+     * デバッグログ文字列の取得
+     * @return デバッグログ文字列
+     */
+    public String toLogString() {
+        StringBuilder ret = new StringBuilder();
+        ret.append(String.format(Locale.JAPAN,"bus_route_no=%s\n", bus_route_no));
+        ret.append(String.format(Locale.JAPAN," bus_route_name=%s\n", bus_route_name));
+        return ret.toString();
+    }
+
+    /**
+     * 自身のクローンを返す
+     * @return 自身のクローン
+     */
+    @Override
+    public busRoute_info clone() {
+        busRoute_info ret = null;
+        try {
+            ret = (busRoute_info)super.clone();
+
+        } catch (CloneNotSupportedException ex) {
+            EsnDebugLog.outputLog("clone失敗 " + ex.toString(), EsnDebugLog.DIV.ERR);
+        }
+        return ret;
+    }
+
+    /**
+     * 同値判定(Android Studioの自動生成機能を使用)
+     * @param o 比較対象オブジェクト
+     * @return true:同じ false:異なる
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        busRoute_info that = (busRoute_info) o;
+        return bus_route_no.equals(that.bus_route_no) &&
+                bus_route_name.equals(that.bus_route_name);
+    }
+
+    /**
+     * ハッシュコードを取得(Android Studioの自動生成機能を使用)
+     * @return ハッシュコード
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(bus_route_no, bus_route_name);
+    }
+    //endregion
+}
+

+ 163 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/busRoute_infos.java

@@ -0,0 +1,163 @@
+package jp.co.ecosysnetwork.ccloca_app.models;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+public class busRoute_infos {
+
+    //region 外部公開メンバ変数
+    /**
+     * コース情報リスト
+     */
+    public List<busRoute_info> items;
+    //endregion
+
+    //region 生成と廃棄
+    /*
+     *  コンストラクタ
+     */
+    public busRoute_infos()
+    {
+        items = new ArrayList<>();
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+
+        busRoute_info info = new busRoute_info();
+        info.bus_route_no = "0";
+        info.bus_route_name = "コースを選択してください";
+        items.add(info);
+
+        // コース情報数の取得
+        int infoNum = pref.getInt(MyApplication.PKEY_COURSE_INFO_NUM, 0);
+        for (int i=0; i<infoNum; i++) {
+            info = new busRoute_info();
+
+            String key = String.format(Locale.JAPAN, MyApplication.PKEY_BUS_ROUTE_INFO_ID, i);
+            info.bus_route_no = pref.getString(key, "0");
+
+            key = String.format(Locale.JAPAN, MyApplication.PKEY_BUS_ROUTE_INFO_NAME, i);
+            info.bus_route_name = pref.getString(key, "");
+
+            if (!info.bus_route_no.equals("0"))
+            {
+                items.add(info);
+            }
+        }
+    }
+    /**
+     * デバッグログ文字列の取得
+     * @return デバッグログ文字列
+     */
+    public String toLogString() {
+        StringBuilder ret = new StringBuilder();
+        for (busRoute_info info : items) {
+            ret.append(info.toLogString());
+        }
+        return ret.toString();
+    }
+    /**
+     * 自身のクローンを返す
+     * @return 自身のクローン
+     */
+    @Override
+    public busRoute_infos clone() {
+        busRoute_infos ret = null;
+        try {
+            ret = (busRoute_infos)super.clone();
+            ret.items = new ArrayList<>();
+            for (busRoute_info info : this.items) {
+                ret.items.add(info.clone());
+            }
+        } catch (CloneNotSupportedException ex) {
+            EsnDebugLog.outputLog("clone失敗 " + ex.toString(), EsnDebugLog.DIV.ERR);
+        }
+        return ret;
+    }
+
+    /**
+     * 同値判定(Android Studioの自動生成機能を使用)
+     * @param o 比較対象オブジェクト
+     * @return true:同じ false:異なる
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        busRoute_infos that = (busRoute_infos) o;
+        return items.equals(that.items);
+    }
+
+    /**
+     * ハッシュコードを取得(Android Studioの自動生成機能を使用)
+     * @return ハッシュコード
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(items);
+    }
+    /**
+     * プリファレンスへの保存
+     * @return true:成功 false:失敗
+     */
+    public boolean savePreference() {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+
+        // コース情報数の登録
+        editor.putInt(MyApplication.PKEY_COURSE_INFO_NUM, items.size());
+        for (int i=0; i<items.size(); i++) {
+            busRoute_info info = items.get(i);
+
+            String key = String.format(Locale.JAPAN, MyApplication.PKEY_BUS_ROUTE_INFO_ID, i);
+            editor.putString(key, info.bus_route_no);
+
+            key = String.format(Locale.JAPAN, MyApplication.PKEY_BUS_ROUTE_INFO_NAME, i);
+            editor.putString(key, info.bus_route_name);
+        }
+        return editor.commit();
+    }
+    /**
+     * プリファレンスへの保存
+     */
+    public void initBusRouteInfos() {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+
+        // コース情報数の登録
+        editor.putInt(MyApplication.PKEY_COURSE_INFO_NUM, 0);
+        for (int i=0; i<items.size(); i++) {
+            String key = String.format(Locale.JAPAN, MyApplication.PKEY_BUS_ROUTE_INFO_ID, i);
+            editor.putString(key, null);
+
+            key = String.format(Locale.JAPAN, MyApplication.PKEY_BUS_ROUTE_INFO_NAME, i);
+            editor.putString(key, null);
+        }
+        editor.commit();
+    }
+    /**
+     * コース情報をIDから取得
+     * @param bus_route_no コースキー
+     * @return コース情報
+     */
+    public busRoute_info getCourseInfoFromInfoBusRouteNo(String bus_route_no) {
+        busRoute_info ret = null;
+        for (busRoute_info info : items) {
+            if (info.bus_route_no.equals(bus_route_no) )  {
+                ret = info;
+                break;
+            }
+        }
+        return ret;
+    }
+    //endregion
+}

+ 106 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/course_settings.java

@@ -0,0 +1,106 @@
+package jp.co.ecosysnetwork.ccloca_app.models;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+/**
+ * コース設定モデル
+ */
+public class course_settings implements Cloneable {
+    //region 外部公開メンバ変数
+    /**
+     * 選択中コース情報モデル
+     */
+    public busRoute_info selected_busRoute_info;
+    /**
+     * 選択中コース情報モデルポジション
+     */
+    public int selected_position;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     */
+    public course_settings() {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        selected_busRoute_info = new busRoute_info();
+        selected_busRoute_info.bus_route_no = pref.getString(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_ID, "0");
+        selected_busRoute_info.bus_route_name = pref.getString(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_NAME, "コースを選択してください。");
+        selected_position = pref.getInt(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_POSITION, 0);
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * デバッグログ文字列の取得
+     * @return デバッグログ文字列
+     */
+    public String toLogString() {
+        return String.format(Locale.JAPAN," BusRouteId = %s\n BusRouteName = %s\n Position = %d", selected_busRoute_info.bus_route_no, selected_busRoute_info.bus_route_name, selected_position);
+    }
+    /**
+     * 自身のクローンを返す
+     * @return 自身のクローン
+     */
+    @Override
+    public course_settings clone() {
+        course_settings ret = null;
+        try {
+            ret = (course_settings)super.clone();
+            ret.selected_busRoute_info = this.selected_busRoute_info;
+            ret.selected_position = this.selected_position;
+        } catch (CloneNotSupportedException ex) {
+            EsnDebugLog.outputLog("clone失敗 " + ex.toString(), EsnDebugLog.DIV.ERR);
+        }
+        return ret;
+    }
+    /**
+     * 同値判定(Android Studioの自動生成機能を使用)
+     * @param o 比較対象オブジェクト
+     * @return true:同じ false:異なる
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        course_settings that = (course_settings) o;
+        return (selected_busRoute_info.equals(that.selected_busRoute_info) && (selected_position == that.selected_position));
+    }
+    /**
+     * プリファレンスへの保存
+     * @return true:成功 false:失敗
+     */
+    public boolean savePreference() {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+
+        editor.putString(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_ID, selected_busRoute_info.bus_route_no);
+        editor.putString(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_NAME, selected_busRoute_info.bus_route_name);
+        editor.putInt(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_POSITION, selected_position);
+        return editor.commit();
+    }
+    /**
+     * 設定コースの初期化
+     * @return true:成功 false:失敗
+     */
+    public boolean initSelectedCourse() {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+
+        editor.putString(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_ID, "0");
+        editor.putString(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_NAME, "コースを選択してください");
+        editor.putInt(MyApplication.PKEY_SELECTED_BUS_ROUTE_INFO_POSITION, 0);
+        return editor.commit();
+    }
+    //endregion
+}

+ 253 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/drive_state.java

@@ -0,0 +1,253 @@
+package jp.co.ecosysnetwork.ccloca_app.models;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+/**
+ * 運転状態モデル
+ */
+public class drive_state {
+    //region 列挙体
+    /**
+     * 運行情報
+     */
+    public enum OPERATION_STATE {
+        /**
+         * 運行外
+         */
+        INACTIVE,
+        /**
+         * 運行中
+         */
+        ACTIVE
+    }
+    //endregion
+    //region メンバ変数
+    /**
+     * 排他制御用オブジェクト
+     */
+    private final Object _lockObj = new Object();
+    /**
+     * プリファレンス
+     */
+    private final SharedPreferences _pref;
+    /**
+     * プリファレンスエディタ
+     */
+    private final SharedPreferences.Editor _edit;
+    /**
+     * 最終運転日(yyyyMMdd形式の文字列)
+     */
+    private String last_update_course_date;
+    /**
+     * 起動後、位置情報を1回でも受信したか否か
+     */
+    private boolean _location_recved_first;
+    /**
+     * 現在のコースインデックス
+     */
+    private int _now_course_index;
+    /**
+     * 運行状態
+     */
+    private OPERATION_STATE _operation_state;
+    /**
+     * サーバ通信が最終動作した時間
+     */
+    private long _lastCommActiveMsec;
+    /**
+     * 位置情報取得が最終動作した時間
+     */
+    private long _lastLocationActiveMsec;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     */
+    @SuppressLint("CommitPrefEdits")
+    public drive_state(SharedPreferences pref) {
+        _pref = pref;
+        _edit = _pref.edit();
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * デバッグログ文字列の取得
+     * @return デバッグログ文字列
+     */
+    public String toLogString() {
+        String ret = String.format(Locale.JAPAN, " last_update_course_date=%s\n", last_update_course_date) +
+                String.format(Locale.JAPAN, " _location_recved_first=%s\n", _location_recved_first) +
+                String.format(Locale.JAPAN, " _now_course_index=%d\n", _now_course_index) +
+                String.format(Locale.JAPAN, " _operation_state=%d\n", _operation_state.ordinal());
+        return ret;
+    }
+    /**
+     * 初期化処理
+     */
+    public void init() {
+        synchronized (_lockObj) {
+            last_update_course_date = _pref.getString(MyApplication.PKEYlast_update_course_date, "20000101");
+            _location_recved_first = false;
+            // 運行状態パース
+            try {
+                _operation_state = OPERATION_STATE.values()[_pref.getInt(MyApplication.PKEY_OPERATION_STATE, OPERATION_STATE.INACTIVE.ordinal())];
+            }
+            catch (Exception ex) {
+                _operation_state = OPERATION_STATE.INACTIVE;
+                EsnDebugLog.outputLog(String.format(Locale.JAPAN, "_operation_state load error %s", ex.toString()), EsnDebugLog.DIV.ERR);
+            }
+            _lastCommActiveMsec = 0;
+            _lastLocationActiveMsec = 0;
+        }
+    }
+    //region 最終運転日(yyyyMMdd形式の文字列)
+    /**
+     * 最終運転日(yyyyMMdd形式の文字列)の取得
+     * @return 現最終運転日(yyyyMMdd形式の文字列)
+     */
+    public String getLastUpdateCourseDate() {
+        synchronized (_lockObj) {
+            return last_update_course_date;
+        }
+    }
+    /**
+     * 最終運転日(yyyyMMdd形式の文字列)の設定
+     * @param val 最終運転日(yyyyMMdd形式の文字列)
+     * @param isCommit プリファレンスにコミットするか否か
+     */
+    public void setLastUpdateCourseDate(String val, boolean isCommit) {
+        synchronized (_lockObj) {
+            if ((last_update_course_date == null) || (!last_update_course_date.equals(val))) {
+                last_update_course_date = val;
+                _edit.putString(MyApplication.PKEYlast_update_course_date, last_update_course_date);
+                if (isCommit) {
+                    _edit.commit();
+                }
+            }
+        }
+    }
+    //endregion
+    //region 起動後、位置情報を1回でも受信したか否か
+    /**
+     * 起動後、位置情報を1回でも受信したか否かの取得
+     * @return 起動後、位置情報を1回でも受信したか否か
+     */
+    public boolean getLocationRecvedFirst() {
+        synchronized (_lockObj) {
+            return _location_recved_first;
+        }
+    }
+    /**
+     * 起動後、位置情報を1回でも受信したか否かの設定
+     * @param val 起動後、位置情報を1回でも受信したか否か
+     */
+    public void setLocationRecvedFirst(boolean val) {
+        synchronized (_lockObj) {
+             _location_recved_first = val;
+        }
+    }
+    //endregion
+    //region 現在のコースインデックス
+    /**
+     * 現在のコースインデックスの取得
+     * @return 現在のコースインデックス
+     */
+    public int getNowCourseIndex() {
+        synchronized (_lockObj) {
+            return _now_course_index;
+        }
+    }
+    /**
+     * 現在のコースインデックスの設定
+     * @param val 現在のコースインデックス
+     * @param isCommit プリファレンスにコミットするか否か
+     */
+    public void setNowCourseIndex(int val, boolean isCommit) {
+        synchronized (_lockObj) {
+            if (_now_course_index != val) {
+                _now_course_index = val;
+                _edit.putInt(MyApplication.PKEY_NOW_COURSE_INDEX, _now_course_index);
+                if (isCommit) {
+                    _edit.commit();
+                }
+            }
+        }
+    }
+    //endregion
+    //region 運行状態
+    /**
+     * 運行状態の取得
+     * @return 運行状態
+     */
+    public OPERATION_STATE getOperationState() {
+        synchronized (_lockObj) {
+            return _operation_state;
+        }
+    }
+    /**
+     * 運行状態の設定
+     * @param val 運行状態
+     * @param isCommit プリファレンスにコミットするか否か
+     */
+    public void setOperationState(OPERATION_STATE val, boolean isCommit) {
+        synchronized (_lockObj) {
+            if (_operation_state != val) {
+                _operation_state = val;
+                _edit.putInt(MyApplication.PKEY_OPERATION_STATE, _operation_state.ordinal());
+                if (isCommit) {
+                    _edit.commit();
+                }
+            }
+        }
+    }
+    //endregion
+    //region サーバ通信が最終動作した時間
+    /**
+     * サーバ通信が最終動作した時間の取得
+     * @return サーバ通信が最終動作した時間
+     */
+    public long getLastCommActiveMsec() {
+        synchronized (_lockObj) {
+            return _lastCommActiveMsec;
+        }
+    }
+    /**
+     * サーバ通信が最終動作した時間の設定
+     * @param val サーバ通信が最終動作した時間
+     */
+    public void setLastCommActiveMsec(long val) {
+        synchronized (_lockObj) {
+            _lastCommActiveMsec = val;
+        }
+    }
+    //endregion
+    //region 位置情報取得が最終動作した時間
+    /**
+     * 位置情報取得が最終動作した時間の取得
+     * @return 位置情報取得が最終動作した時間
+     */
+    public long getLastLocationActiveMsec() {
+        synchronized (_lockObj) {
+            return _lastLocationActiveMsec;
+        }
+    }
+    /**
+     * 位置情報取得が最終動作した時間の設定
+     * @param val 位置情報取得が最終動作した時間
+     */
+    public void setLastLocationActiveMsec(long val) {
+        synchronized (_lockObj) {
+            _lastLocationActiveMsec = val;
+        }
+    }
+    //endregion
+}

+ 47 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/location_send.java

@@ -0,0 +1,47 @@
+package jp.co.ecosysnetwork.ccloca_app.models;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class location_send
+{
+    //region 外部公開メンバ変数
+    /**
+     * 携帯端末NO
+     */
+    public String phone_no;
+    /**
+     * バス経路No
+     */
+    public String bus_route_no;
+    /**
+     * 緯度
+     */
+    public double latitude;
+    /**
+     * 経度
+     */
+    public double longitude;
+    /**
+     *  日付
+     */
+    public String date;
+    /**
+     * メール送信回数
+     */
+    public int mail_send_cnt;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     */
+    public location_send() {
+        phone_no = "0";
+        bus_route_no = "0";
+        latitude = 0;
+        longitude = 0;
+        date = null;
+        mail_send_cnt = 0;
+    }
+    //endregion
+}

+ 196 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/models/system_settings.java

@@ -0,0 +1,196 @@
+package jp.co.ecosysnetwork.ccloca_app.models;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+/**
+ * システム設定モデル
+ */
+public class system_settings implements Cloneable {
+    //region 列挙体
+    /**
+     * 保存項目
+     */
+    public enum SAVE_ITEM {
+        /**
+         * 全て
+         */
+        ALL,
+        /**
+         * 携帯端末ID以外
+         */
+        EXC_USER_ID,
+        /**
+         * 携帯端末IDのみ
+         */
+        ONLY_PHONE_ID
+    }
+    //endregion
+    //region 外部公開メンバ変数
+    /**
+     * サーバURL
+     */
+    public String server_url;
+    /**
+     * 更新間隔
+     */
+    public int update_cycle_msec;
+    /**
+     * 携帯端末ID
+     */
+    public String phone_id;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     */
+    public system_settings() {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        server_url = pref.getString(MyApplication.PKEY_SERVER_URL, "http://10.0.2.2/bus");
+        update_cycle_msec = pref.getInt(MyApplication.PKEY_UPDATE_CYCLE_MSEC, 10000);
+        phone_id = pref.getString(MyApplication.PKEY_PHONE_ID, "");
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * デバッグログ文字列の取得
+     * @return デバッグログ文字列
+     */
+    public String toLogString() {
+        StringBuilder ret = new StringBuilder();
+        ret.append(String.format(Locale.JAPAN," server_url=%s\n", server_url));
+        ret.append(String.format(Locale.JAPAN," update_cycle_msec=%d\n", update_cycle_msec));
+        ret.append(String.format(Locale.JAPAN," phone_id=%s\n", phone_id));
+        return ret.toString();
+    }
+    /**
+     * 自身のクローンを返す
+     * @return 自身のクローン
+     */
+    @Override
+    public system_settings clone() {
+        system_settings ret = null;
+        try {
+            ret = (system_settings)super.clone();
+        } catch (CloneNotSupportedException ex) {
+            EsnDebugLog.outputLog("clone失敗 " + ex.toString(), EsnDebugLog.DIV.ERR);
+        }
+        return ret;
+    }
+    /**
+     * コピー処理
+     * @param to コピー先
+     * @param saveItem 保存項目
+     */
+    public void copy(system_settings to, SAVE_ITEM saveItem) {
+        // サーバURL
+        if ((saveItem == SAVE_ITEM.ALL) || (saveItem == SAVE_ITEM.EXC_USER_ID)) {
+            to.server_url = new String(this.server_url);
+        }
+
+        // 更新間隔
+        if ((saveItem == SAVE_ITEM.ALL) || (saveItem == SAVE_ITEM.EXC_USER_ID)) {
+            to.update_cycle_msec = this.update_cycle_msec;
+        }
+
+        // 携帯端末ID
+        if ((saveItem == SAVE_ITEM.ALL) || (saveItem == SAVE_ITEM.ONLY_PHONE_ID)) {
+            to.phone_id = new String(this.phone_id);
+        }
+    }
+    /**
+     * 同値判定(Android Studioの自動生成機能を使用)
+     * @param o 比較対象オブジェクト
+     * @return true:同じ false:異なる
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        system_settings that = (system_settings) o;
+        return update_cycle_msec == that.update_cycle_msec &&
+                Objects.equals(server_url, that.server_url) &&
+                Objects.equals(phone_id, that.phone_id)
+                ;
+    }
+    /**
+     * 同値判定(Android Studioの自動生成機能を使用)
+     * @param o 比較対象オブジェクト
+     * @param saveItem 保存項目
+     * @return true:同じ false:異なる
+     */
+    public boolean equals(Object o, SAVE_ITEM saveItem) {
+        if (saveItem == SAVE_ITEM.ALL) {
+            return this.equals(o);
+        }
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        system_settings that = (system_settings) o;
+
+        // サーバURL
+        if (saveItem == SAVE_ITEM.EXC_USER_ID) {
+            if (!Objects.equals(server_url, that.server_url)) {
+                return false;
+            }
+        }
+
+        // 更新間隔
+        if (saveItem == SAVE_ITEM.EXC_USER_ID) {
+            if (update_cycle_msec != that.update_cycle_msec) {
+                return false;
+            }
+        }
+
+        // 携帯端末ID
+        if (saveItem == SAVE_ITEM.ONLY_PHONE_ID) {
+            if (!Objects.equals(phone_id, that.phone_id)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+    /**
+     * ハッシュコードを取得(Android Studioの自動生成機能を使用)
+     * @return ハッシュコード
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(server_url, update_cycle_msec, phone_id);
+    }
+    /**
+     * プリファレンスへの保存
+     * @param saveItem 保存項目
+     * @return true:成功 false:失敗
+     */
+    public boolean savePreference(SAVE_ITEM saveItem) {
+        Context ctx = MyApplication.getInstance().getApplicationContext();
+        SharedPreferences pref = ctx.getSharedPreferences(MyApplication.PREFERENCE_NAME, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+
+        // サーバURL
+        if ((saveItem == SAVE_ITEM.ALL) || (saveItem == SAVE_ITEM.EXC_USER_ID)) {
+            editor.putString(MyApplication.PKEY_SERVER_URL, server_url);
+        }
+
+        // 更新間隔
+        if ((saveItem == SAVE_ITEM.ALL) || (saveItem == SAVE_ITEM.EXC_USER_ID)) {
+            editor.putInt(MyApplication.PKEY_UPDATE_CYCLE_MSEC, update_cycle_msec);
+        }
+
+        // 携帯端末ID
+        if ((saveItem == SAVE_ITEM.ALL) || (saveItem == SAVE_ITEM.ONLY_PHONE_ID)) {
+            editor.putString(MyApplication.PKEY_PHONE_ID, phone_id);
+        }
+
+        return editor.commit();
+    }
+    //endregion
+}

+ 317 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/service/DriveService.java

@@ -0,0 +1,317 @@
+package jp.co.ecosysnetwork.ccloca_app.service;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.location.Location;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.core.app.NotificationCompat;
+
+import com.google.android.gms.location.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.ccloca_app.R;
+import jp.co.ecosysnetwork.ccloca_app.models.busRoute_info;
+import jp.co.ecosysnetwork.ccloca_app.models.course_settings;
+import jp.co.ecosysnetwork.ccloca_app.models.drive_state;
+import jp.co.ecosysnetwork.ccloca_app.models.location_send;
+import jp.co.ecosysnetwork.ccloca_app.models.system_settings;
+import jp.co.ecosysnetwork.ccloca_app.thread.CommThread;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+
+/**
+ * 運転サービス
+ */
+public class DriveService extends Service {
+    //region 定数
+    /**
+     * 通知チャネルID
+     */
+    public static final String CHANNEL_ID = "100";
+    /**
+     * 運転サービスID
+     */
+    private static final int SERVICE_ID = 9999;
+    /**
+     * 位置情報を採用する最小精度
+     */
+    private static final float MIN_ACC = 100;
+    private static final long MIN_CYCLE_MSEC_LOCATION_CONTROL = 3000;
+    /**
+     * 初期位置情報取得サイクル
+     */
+    private static final long INIT_MSEC_LOCATION_INTERVAL = 1000;
+    /**
+     * 初期位置情報取得サイクル
+     */
+    private static final long INIT_MSEC_LOCATION_FAST_INTERVAL = 1000;
+    //endregion
+    //region メンバ変数
+    /**
+     * シングルトンのアプリケーション参照
+     */
+    private final MyApplication _app = MyApplication.getInstance();
+    /**
+     * システム設定モデル
+     */
+    private system_settings _system_settings;
+    /**
+     * コース設定モデル
+     */
+    private course_settings _course_settings;
+    /**
+     * 位置情報クライアント
+     */
+    private FusedLocationProviderClient _fusedLocationProviderClient;
+    /**
+     * 位置情報コールバックハンドラ
+     */
+    private LocationCallback _locationCallback;
+    /**
+     * サービスが開始されているか否か
+     */
+    private static boolean _isStarted = false;
+    /**
+     * 位置情報取得中であるか否か
+     */
+    private boolean _isActiveUpdateLocation = false;
+    /**
+     * 前回の位置制御実施時間
+     */
+    private long _preTimeLocationControl = 0;
+//    /**
+//     * サーバ通信サービスが停止していると判断する、サーバとの不通時間
+//     */
+//    private static final int DISABLE_MSEC_COMM = 30 * 1000;
+    //endregion
+    //region 内部メソッド
+    /**
+     * 位置情報による制御
+     * @param lat 緯度
+     * @param lon 経度
+     * @param acc 精度
+     * @param alt 標高
+     */
+    private void locationControl(double lat, double lon, double acc, double alt) {
+        //region 位置制御を実施する時間であるか判定
+        long nowTime = System.currentTimeMillis();
+        if ((nowTime - _preTimeLocationControl) < MIN_CYCLE_MSEC_LOCATION_CONTROL) {
+            //EsnDebugLog.outputLog(String.format(Locale.JAPAN, "規定間隔未満であるため位置制御実施しない now=%d pre=%d", nowTime, _preTimeLocationControl), EsnDebugLog.DIV.INFO);
+            return;
+        }
+        _preTimeLocationControl = nowTime;
+        //endregion
+
+        // 携帯端末IDが未設定の場合は処理しない
+        if ((_system_settings.phone_id == null) || (_system_settings.phone_id.length() == 0)) {
+            EsnDebugLog.outputLog("携帯端末IDが未設定のため運転制御しない", EsnDebugLog.DIV.ERR);
+            return;
+        }
+
+        //region 最初の位置情報受信時、オープニング音声を再生する
+        if (!_app.drive_state_model.getLocationRecvedFirst()) {
+            EsnDebugLog.outputLog("最初の受信", EsnDebugLog.DIV.INFO);
+            _app.drive_state_model.setLocationRecvedFirst(true);
+            _app.playWav(R.raw.opening, 1000);
+        }
+        //endregion
+
+        //region 現在のコースIDを設定
+        busRoute_info busRoute_info = _course_settings.selected_busRoute_info;
+        if (busRoute_info.bus_route_no.equals("0")) {
+            EsnDebugLog.outputLog("現在のコースIDが未設定", EsnDebugLog.DIV.ERR);
+            return;
+        }
+        //endregion
+
+        //region 運転中以外は以降処理しない
+        drive_state.OPERATION_STATE operation_state = _app.drive_state_model.getOperationState();
+        if (operation_state == drive_state.OPERATION_STATE.INACTIVE) {//region 運行外
+            // 運転中以外は以降処理しない
+            EsnDebugLog.outputLog("運行外時に処理が行われました", EsnDebugLog.DIV.ERR);
+            return;
+            //endregion
+        }
+            location_send sendLocation = new location_send();
+
+            // 日替わり時、運行状態をクリアする
+            SimpleDateFormat fmtYYYYMMDD = new SimpleDateFormat("yyyy-MM-dd", Locale.JAPANESE);
+            String sendDate = fmtYYYYMMDD.format(new Date());
+            SimpleDateFormat fmtHHiiss = new SimpleDateFormat("HH:mm:ss", Locale.JAPANESE);
+            String sendTime = fmtHHiiss.format(new Date());
+            // 位置情報をサーバに送信要求
+            sendLocation.phone_no = _system_settings.phone_id;
+            sendLocation.bus_route_no = _course_settings.selected_busRoute_info.bus_route_no;
+            sendLocation.latitude = lat;
+            sendLocation.longitude = lon;
+            sendLocation.date = sendDate + sendTime;
+
+//            // CommServiceを起動し、サーバ送信データリストを引き渡す
+//            Intent intent = new Intent();
+//            intent.putExtra(CommService.EXTRA_POSITION_SENDS, sendList);
+//            CommService.enqueueWork(MyApplication.getInstance(), intent);
+
+            // CommThreadを起動し、サーバにデータ送信
+            CommThread th = new CommThread(_system_settings.server_url, sendLocation);
+            th.start();
+        //endregion
+    }
+    /**
+     * 位置情報取得の開始
+     * @param interval 位置情報の更新間間隔(単位:ミリ秒)
+     * @param fastestInterval 位置情報の最短更新間隔(単位:ミリ秒)
+     */
+    @SuppressLint("MissingPermission")
+    private void startLocationUpdates(long interval, long fastestInterval) {
+        // 位置情報取得中の場合は一旦停止する
+        if (_isActiveUpdateLocation) {
+            stopLocationUpdates();
+        }
+        // 位置情報の取得を開始する
+        LocationRequest locReq = LocationRequest.create();
+        locReq.setInterval(interval);
+        locReq.setFastestInterval(fastestInterval);
+        locReq.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+        _fusedLocationProviderClient.requestLocationUpdates(locReq, _locationCallback, Looper.getMainLooper());
+        _isActiveUpdateLocation = true;
+
+        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "位置情報の取得開始 interval=%d fastestInterval=%d", interval, fastestInterval), EsnDebugLog.DIV.INFO);
+    }
+    /**
+     * 位置情報取得の停止
+     */
+    private void stopLocationUpdates() {
+        if (_isActiveUpdateLocation) {
+            EsnDebugLog.outputLog("位置情報の更新を停止", EsnDebugLog.DIV.INFO);
+            _fusedLocationProviderClient.removeLocationUpdates(_locationCallback);
+            _isActiveUpdateLocation = false;
+        }
+        else {
+            EsnDebugLog.outputLog("位置情報の更新は既に停止している", EsnDebugLog.DIV.WARN);
+        }
+    }
+    //endregion
+    //region イベントメソッド
+    /**
+     * サービス作成イベント
+     */
+    @Override
+    public void onCreate() {
+        EsnDebugLog.outputLog("運転サービス onCreate", EsnDebugLog.DIV.INFO);
+        super.onCreate();
+
+        // メンバ変数設定
+        _system_settings = new system_settings();
+        _course_settings = new course_settings();
+
+        // 位置情報クライアント生成
+        _fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
+    }
+    /**
+     * サービスの開始イベント
+     * @param intent インテント
+     * @param flags フラグ
+     * @param startId 開始ID
+     * @return  START_STICKY固定
+     */
+    @SuppressLint("WakelockTimeout")
+    @Override
+    public int onStartCommand(final Intent intent, int flags, int startId) {
+        EsnDebugLog.outputLog("運転サービス onStartCommand", EsnDebugLog.DIV.INFO);
+
+        // サービス開始フラグON
+        _isStarted = true;
+
+        // ロケーションコールバック定義
+        _locationCallback = new LocationCallback() {
+            /**
+             * ロケーション取得結果
+             *
+             * @param locationResult 位置情報の取得結果
+             */
+            @Override
+            public void onLocationResult(LocationResult locationResult) {
+                if (locationResult != null) {
+                    for (Location loc : locationResult.getLocations()) {
+                        double lat = loc.getLatitude();     // 緯度
+                        double lon = loc.getLongitude();    // 経度
+                        float acc = loc.getAccuracy();      // 精度
+                        double alt = loc.getAltitude();      // 標高
+                        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "lat=%f lon=%f acc=%f alt=%f", lat, lon, acc, alt), EsnDebugLog.DIV.INFO);
+
+                        _app.drive_state_model.setLastLocationActiveMsec(System.currentTimeMillis());
+
+                        // 取得値が最低精度を満たしている場合、位置情報制御を実施する
+                        if ((acc < MIN_ACC) && (0 < lat) && (0 < lon)) {
+                            locationControl(lat, lon, acc, alt);
+                        }
+                    }
+                }
+                super.onLocationResult(locationResult);
+            }
+        };
+
+        // 通知インテントの作成
+        PendingIntent openIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setContentTitle(MyApplication.getInstance().getString(R.string.driveNotifyName2))
+                .setContentText(MyApplication.getInstance().getString(R.string.driveNotifyDescription2))
+                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                .setContentIntent(openIntent)
+                .build();
+
+        // サービスのフォアグラウンド化
+        startForeground(SERVICE_ID, notification);
+
+        // 位置情報取得開始
+        startLocationUpdates(_system_settings.update_cycle_msec, _system_settings.update_cycle_msec);
+
+        return START_STICKY;
+    }
+    /**
+     * サービスのバインドイベント
+     * @param intent インテント
+     * @return null固定
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        EsnDebugLog.outputLog("運転サービス onBind", EsnDebugLog.DIV.INFO);
+        return null;
+    }
+    /**
+     * サービスの廃棄イベント
+     */
+    @Override
+    public void onDestroy() {
+        EsnDebugLog.outputLog("運転サービス onDestroy", EsnDebugLog.DIV.INFO);
+        super.onDestroy();
+
+//        // 通信サービスの終了
+//        CommService.requestStop();
+
+        stopLocationUpdates();
+        stopForeground(Service.STOP_FOREGROUND_DETACH);
+        stopSelf();
+        // サービス開始フラグOFF
+        _isStarted = false;
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * サービスが開始されているか否かを返す
+     * @return サービスが開始されているか否か
+     */
+    public static boolean isStarted() {
+        return _isStarted;
+    }
+    //endregion
+}

+ 243 - 0
app/src/main/java/jp/co/ecosysnetwork/ccloca_app/thread/CommThread.java

@@ -0,0 +1,243 @@
+package jp.co.ecosysnetwork.ccloca_app.thread;
+
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigDecimal;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Locale;
+
+import jp.co.ecosysnetwork.ccloca_app.MyApplication;
+import jp.co.ecosysnetwork.ccloca_app.models.location_send;
+import jp.co.ecosysnetwork.universal.EsnDebugLog;
+import jp.co.ecosysnetwork.universal.EsnQueue;
+import jp.co.ecosysnetwork.universal.EsnXmlParser;
+
+/**
+ * サーバ通信スレッド
+ */
+public class CommThread  extends Thread
+{
+    //region 列挙体
+    /**
+     * 送信結果
+     */
+    private enum SEND_RESULT {
+        /**
+         * 成功
+         */
+        SUCCESS,
+        /**
+         * エラー
+         */
+        ERROR,
+        /**
+         * 携帯端末情報なし
+         */
+        NOT_EXIST_USER
+    }
+    //endregion
+    //region 定数
+    /**
+     * 送信タイムアウト時間
+     */
+    private static final int SEND_TIMEOUT_MSEC = 5000;
+    /**
+     * 送信後の待機時間
+     */
+    private static final int WAIT_MSEC_SEND = 200;
+    /**
+     * メール再送信の最大回数
+     */
+    private static final int MAIL_SEND_RETRY_MAX = 20;
+    /***
+     * 送信実行間隔
+     */
+    private static final int EXECUTE_CYECLE_MSEC = 1000;
+    //endregion
+    //region メンバ変数
+    /**
+     * シングルトンのアプリケーション参照
+     */
+    private static final MyApplication _app = MyApplication.getInstance();
+    /**
+     * サーバURL
+     */
+    private String _url;
+    /**
+     * サーバ送信情報リスト
+     */
+    private location_send _location_send;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     * @param list サーバ送信情報リスト
+     */
+    public CommThread(String url, location_send list) {
+        super();
+        _url = url;
+        _location_send = list;
+    }
+    //endregion
+    //region 内部メソッド
+    /**
+     * サーバへの送信処理
+     * @param location_send location_send
+     * @return 送信結果
+     */
+    private SEND_RESULT sendExec(location_send location_send) {
+        SEND_RESULT ret = SEND_RESULT.ERROR;
+        // ログ出力処理開始タイム
+        long startMsec = System.currentTimeMillis();
+        // 例外エラー発生時にどこまで進んでいたかを判別するためのフラグ
+        int step = 0;
+
+        HttpURLConnection con = null;
+        DataOutputStream os = null;
+        InputStream is = null;
+        InputStreamReader isr = null;
+        BufferedReader br = null;
+        try {
+            URL accUrl = new URL(_url + MyApplication.APIURL_SEND_BUS_LOCATION);   step = 1;
+            con = (HttpURLConnection)accUrl.openConnection();       step = 2;
+            con.setConnectTimeout(SEND_TIMEOUT_MSEC);               step = 3;
+            con.setReadTimeout(SEND_TIMEOUT_MSEC);                  step = 4;
+            con.setRequestMethod("POST");                           step = 5;
+            con.setDoOutput(true);                                  step = 6;
+            con.setDoInput(true);                                   step = 7;
+            con.setUseCaches(false);                                step = 8;
+            // ヘッダ設定
+            con.setRequestProperty("ANDROID-API-KEY", MyApplication.API_KEY); step = 9;
+            con.setRequestProperty("Connection", "Keep-Alive");     step = 10;
+            con.setRequestProperty("Charset", "UTF-8");             step = 11;
+            String boundary = "-----------------------------" + System.currentTimeMillis(); step = 12;
+            con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+            con.connect();                                          step = 13;
+            // 送信データの設定
+            os = new DataOutputStream(con.getOutputStream());       step = 14;
+            append_string(os, boundary, "intFacilityPhoneNo", location_send.phone_no); step = 15;
+            append_string(os, boundary, "intBusRouteNo", location_send.bus_route_no);  step = 16;
+            append_string(os, boundary, "decLatitude", BigDecimal.valueOf(location_send.latitude).toPlainString()); step = 17;
+            append_string(os, boundary, "decLongitude", BigDecimal.valueOf(location_send.longitude).toPlainString()); step = 18;
+            append_string(os, boundary, "dtDate", location_send.date.toString()); step = 19;
+            append_final(os, boundary);                             step = 20;
+            os = null;                                              step = 21;
+            MyApplication.sleep(WAIT_MSEC_SEND);                    step = 22;
+            // 受信データを取得
+            is = con.getInputStream();                              step = 23;
+            // 受信データのXMLパース
+            XmlPullParser parser = Xml.newPullParser();             step = 24;
+            parser.setInput(is, "UTF-8");               step = 25;
+
+            // 受信データのパース
+            EsnXmlParser esnParser = new EsnXmlParser(parser);      step = 26;
+            String retParse = esnParser.tryParse();                 step = 27;
+            ret = SEND_RESULT.SUCCESS;                              step = 28;
+            String errMsg = "";                                     step = 29;
+            if (retParse == null) {
+                // パース成功
+                for (EsnXmlParser.ParseItem item : esnParser.getParseItems()) {
+                    if (item.tag.equals("errMsg")) {
+                        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "ret=%s phoneNo=%s busRouteNo = %s", item.value, location_send.phone_no, location_send.bus_route_no), EsnDebugLog.DIV.INFO);
+                        errMsg = item.value;
+                        ret = SEND_RESULT.ERROR;
+                        break;
+                    }
+                }
+            }                                                       step = 30;
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "ret=%s recv=%s time=%d url=%s", ret.toString(), errMsg, System.currentTimeMillis() - startMsec, _url), EsnDebugLog.DIV.INFO);
+        }
+        catch (Exception e1) {
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "例外エラー step=%d time=%d %s", step, System.currentTimeMillis() - startMsec, e1), EsnDebugLog.DIV.ERR);
+            ret = SEND_RESULT.ERROR;
+        }
+        finally {
+            try {if (br != null) br.close();} catch (Exception e2) {EsnDebugLog.outputLog(String.format(Locale.JAPAN, "br.close()エラー %s", e2.toString()), EsnDebugLog.DIV.ERR);}
+            try {if (isr != null) isr.close();} catch (Exception e2) {EsnDebugLog.outputLog(String.format(Locale.JAPAN, "isr.close()エラー %s", e2.toString()), EsnDebugLog.DIV.ERR);}
+            try {if (os != null) os.close();} catch (Exception e2) {EsnDebugLog.outputLog(String.format(Locale.JAPAN, "os.close()エラー %s", e2.toString()), EsnDebugLog.DIV.ERR);}
+            try {if (is != null) is.close();} catch (Exception e2) {EsnDebugLog.outputLog(String.format(Locale.JAPAN, "is.close()エラー %s", e2.toString()), EsnDebugLog.DIV.ERR);}
+            try {if (con != null) con.disconnect();} catch (Exception e2) {EsnDebugLog.outputLog(String.format(Locale.JAPAN, "con.disconnect()エラー %s", e2.toString()), EsnDebugLog.DIV.ERR);}
+        }
+        return ret;
+    }
+    /**
+     * 送信文字列の追加
+     * @param os データ出力ストリーム
+     * @param boundary 境界
+     * @param name 属性名
+     * @param value 属性値
+     * @throws IOException 入出力例外を返すことがある
+     */
+    protected void append_string(DataOutputStream os, String boundary, String name, String value) throws IOException {
+        os.writeBytes("--" + boundary + "\r\n");
+        os.writeBytes("Content-Disposition: form-data; name=" + "\"" + name + "\"" + "\r\n" + "\r\n");
+        os.writeBytes(value + "\r\n");
+    }
+    /**
+     * 送信データの終端設定
+     * @param os データ入出力ストリーム
+     * @param boundary 境界
+     * @throws IOException 入出力例外を返すことがある
+     */
+    protected void append_final(DataOutputStream os, String boundary) throws IOException {
+        os.writeBytes("--" + boundary + "--" + "\r\n");
+        os.close();
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * サーバ通信処理
+     */
+    @Override
+    public void run() {
+        // 引き渡された送信データをキューに設定
+        EsnQueue<location_send> location_send_queue = new EsnQueue<location_send>(100);
+        if (_location_send != null) {
+            location_send_queue.put(_location_send);
+            EsnDebugLog.outputLog(String.format(Locale.JAPAN, "サーバ通信指示データ busRouteNo=%s", _location_send.bus_route_no), EsnDebugLog.DIV.INFO);
+        }
+        else {
+            EsnDebugLog.outputLog("引き渡された送信データがnull", EsnDebugLog.DIV.ERR);
+        }
+
+        while (true) {
+            // メッセージキューを取得
+            location_send ps = location_send_queue.get();
+            if (ps == null) {
+                break;
+            }
+
+            // 前回時間
+            _app.drive_state_model.setLastCommActiveMsec(System.currentTimeMillis());
+
+            // サーバへの送信処理
+            SEND_RESULT sendRet = sendExec(_location_send);
+
+            if (sendRet == SEND_RESULT.ERROR) {
+                if (ps.mail_send_cnt < MAIL_SEND_RETRY_MAX) {
+                    ps.mail_send_cnt++;
+                    if (location_send_queue.put(ps)) {
+                        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "メール再送信 bus_route_no=%s cnt=%d", ps.bus_route_no, ps.mail_send_cnt), EsnDebugLog.DIV.WARN);
+                    }
+                    else {
+                        EsnDebugLog.outputLog(String.format(Locale.JAPAN, "キュー一杯のためメール再送信できず bus_route_no=%s cnt=%d", ps.bus_route_no,ps.mail_send_cnt), EsnDebugLog.DIV.ERR);
+                    }
+                }
+                else {
+                    EsnDebugLog.outputLog(String.format(Locale.JAPAN, "メール再送信回数オーバー bus_route_no=%s cnt=%d", ps.bus_route_no,ps.mail_send_cnt), EsnDebugLog.DIV.ERR);
+                }
+            }
+            MyApplication.sleep(EXECUTE_CYECLE_MSEC);
+        }
+        EsnDebugLog.outputLog("サーバ通信終了", EsnDebugLog.DIV.INFO);
+    }
+    //endregion
+}

+ 246 - 0
app/src/main/java/jp/co/ecosysnetwork/universal/EsnDebugLog.java

@@ -0,0 +1,246 @@
+package jp.co.ecosysnetwork.universal;
+
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Calendar;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * ECOSYSNETWORK K.K. デバッグログ出力クラス
+ */
+public class EsnDebugLog {
+    //region 列挙体
+
+    /**
+     * ログ区分
+     */
+    public enum DIV {
+        /**
+         * デバッグ
+         */
+        DBG,
+        /**
+         * 情報
+         */
+        INFO,
+        /**
+         * 警告
+         */
+        WARN,
+        /**
+         * エラー
+         */
+        ERR
+    }
+    //endregion
+    //region 定数
+    /**
+     * 日付部のフォーマット。拡張子の前に設定する。
+     */
+    private static final String DATE_FORMAT = "yyyyMMdd";
+    //endregion
+    //region 内部変数
+    /**
+     * 排他制御用オブジェクト
+     */
+    private static final Object _lockObj = new Object();
+    /**
+     * ログファイル名(ディレクトリ名。末尾に\をつける)
+     */
+    private static String _logFileNameDir;
+    /**
+     * ログファイル名(拡張子前)
+     */
+    private static String _logFileNameBody;
+    /**
+     * ログファイル名(拡張子。.を含む)
+     */
+    private static String _logFileNameExt;
+    /**
+     * ログファイル保持日数
+     */
+    private static int _logKeepDays;
+    /**
+     * 出力するログレベル(設定された値以上のログを出力する)
+     */
+    private static DIV _logLevel;
+    //endregion
+    //region 内部メソッド
+
+    /**
+     * 保持日数を過ぎたログファイルを削除する
+     */
+    private static void cleanUpLog() {
+        synchronized (_lockObj) {
+            try {
+                File lfd = new File(_logFileNameDir);
+                if (lfd.exists()) {
+                    // ログファイル一覧を取得
+                    String searchPtn = _logFileNameBody + "*" + _logFileNameExt;
+                    EsnFileSearch efs = new EsnFileSearch();
+                    File[] pathes = efs.listFiles(_logFileNameDir, searchPtn);
+
+                    if (pathes != null) {
+                        // 現在日からログファイルを保持する年月日を取得
+                        Calendar cld = Calendar.getInstance();
+                        cld.setTime(new Date());
+                        cld.add(Calendar.DATE, -_logKeepDays);
+                        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.JAPAN);
+                        String limitDate = sdf.format(cld.getTime());
+
+                        // ログファイル一覧から年月日部を抽出し、保持する年月日より古いファイルは削除する
+                        int datePos = _logFileNameBody.length();
+                        for (File path : pathes) {
+                            String pathName = path.getName();
+                            if (pathName.length() >= (datePos + DATE_FORMAT.length())) {
+                                String dateStr = pathName.substring(datePos, datePos + DATE_FORMAT.length());
+                                if (dateStr.compareTo(limitDate) < 0) {
+                                    if (!path.delete()) {
+                                        Log.e(EsnUtility.getClassName(3) + EsnUtility.getMethodName(3), "delete failure." + pathName);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+    //endregion
+    //region 外部公開メソッド
+
+    /**
+     * 初期化処理
+     *
+     * @param dirName     ログファイルのディレクトリ(末尾にフォルダマークつけること)
+     * @param fileName    ログファイル名
+     * @param logKeepDays ログファイルを保持する日数
+     * @param logLevel    出力するログレベル(設定された値以上のログを出力する)
+     */
+    public static void init(String dirName, String fileName, int logKeepDays, DIV logLevel) {
+        // ログファイル情報設定
+        _logFileNameDir = dirName;
+        _logFileNameBody = EsnUtility.getFilePreffix(fileName);
+        _logFileNameExt = "." + EsnUtility.getFileSuffix(fileName);
+
+        // ログファイルを保持する日数
+        _logKeepDays = logKeepDays;
+
+        // ログレベル
+        _logLevel = logLevel;
+
+        // 保持期間を過ぎたログファイルの削除
+        cleanUpLog();
+    }
+
+    /**
+     * ログ出力処理
+     *
+     * @param msg   ログメッセージ
+     * @param div   ログ区分
+     * @param title ログタイトル
+     */
+    private static void outputLog(String msg, DIV div, String title) {
+        // 出力するログレベルでない場合は処理しない
+        if (div.ordinal() < _logLevel.ordinal()) {
+            return;
+        }
+
+        synchronized (_lockObj) {
+            try {
+                // ログディレクトリがない場合、作成する
+                File lfd = new File(_logFileNameDir);
+                if (!lfd.exists()) {
+                    if (!lfd.mkdirs()) {
+                        Log.e(EsnUtility.getClassName(3) + EsnUtility.getMethodName(3), "mkdirs error." + _logFileNameDir);
+                        return;
+                    }
+                }
+
+                // ログファイルフルパス生成
+                Date nowDate = new Date();
+                SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.JAPAN);
+                String path = _logFileNameDir + _logFileNameBody + sdf.format(nowDate.getTime()) + _logFileNameExt;
+
+                // ログメッセージ作成
+                StringBuilder sb = new StringBuilder();
+                sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.JAPAN);
+                sb.append(sdf.format(nowDate.getTime()));
+                switch (div) {
+                    case DBG:
+                        sb.append("【D】");
+                        break;
+                    case INFO:
+                        sb.append("【I】");
+                        break;
+                    case WARN:
+                        sb.append("【W】");
+                        break;
+                    case ERR:
+                        sb.append("【E】");
+                        break;
+                }
+                sb.append("[");
+                sb.append(title);
+                sb.append("]");
+                sb.append(msg);
+                sb.append("\r\n");
+
+                // ログファイル出力
+                FileWriter fw = new FileWriter(path, true);
+                fw.write(sb.toString());
+                fw.close();
+
+                // アウトプットウィンドウへの出力
+                Log.d(EsnUtility.getClassName(3) + EsnUtility.getMethodName(3), sb.toString());
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * デバッグログ出力処理
+     *
+     * @param logMsg ログメッセージ
+     * @param logDiv ログ区分
+     */
+    static public void outputLog(String logMsg, DIV logDiv) {
+        // タイトルの生成
+        String title = EsnUtility.getClassName(4) + "-" + EsnUtility.getMethodName(4);
+        outputLog(logMsg, logDiv, title);
+    }
+
+    /**
+     * ログファイル一覧取得
+     *
+     * @return list ログファイル一覧
+     */
+    static public String[] GetLogFileList() {
+        String[] list = null;
+        try {
+            File lfd = new File(_logFileNameDir);
+            list = lfd.list();
+        } catch (Exception ex) {
+            EsnDebugLog.outputLog("ログファイル一覧取得失敗", EsnDebugLog.DIV.ERR);
+        }
+        return list;
+    }
+
+    /**
+     * ログファイルディレクトリの取得
+     *
+     * @return LOGDIR ログファイルディレクトリ
+     */
+    static public String GetLogDir() {
+        return _logFileNameDir;
+    }
+    //endregion
+}

+ 155 - 0
app/src/main/java/jp/co/ecosysnetwork/universal/EsnFileSearch.java

@@ -0,0 +1,155 @@
+package jp.co.ecosysnetwork.universal;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TreeSet;
+
+/**
+ * ECOSYSNETWORK K.K. ファイル検索クラス
+ */
+public class EsnFileSearch {
+    //region 列挙体
+
+    /**
+     * 検索タイプ
+     */
+    public enum TYPE {
+        /**
+         * ファイル、ディレクトリ両方
+         */
+        BOTH,
+        /**
+         * ファイル
+         */
+        FILE,
+        /**
+         * ディレクトリ
+         */
+        DIR
+    }
+    //endregion
+    //region メンバ変数
+    /**
+     * アルファベット順に並べるためTreeSetを使用。
+     */
+    private final TreeSet<File> _treeSet = new TreeSet<>();
+    //endregion
+    //region 内部メソッド
+
+    /**
+     * 指定条件に該当するファイル、ディレクトリを格納する
+     *
+     * @param type   検索タイプ
+     * @param match  マッチングパターン
+     * @param set    条件に該当するファイル、ディレクトリの格納際
+     * @param file   検査対象のファイル/ディレクトリ
+     * @param period ファイル/ディレクトリ更新後の経過日数
+     */
+    private void addFile(TYPE type, String match, TreeSet<File> set, File file, int period) {
+        // 指定タイプでない場合抜ける
+        if (type == TYPE.FILE) {
+            if (!file.isFile()) {
+                return;
+            }
+        } else if (type == TYPE.DIR) {
+            if (!file.isDirectory()) {
+                return;
+            }
+        }
+        if (match != null && !file.getName().matches(match)) {
+            return;
+        }
+        // 指定日数経過しているかどうかの指定がある場合
+        if (period != 0) {
+            // ファイル更新日付
+            Date lastModifiedDate = new Date(file.lastModified());
+            String lastModifiedDateStr = new SimpleDateFormat("yyyyMMdd", Locale.JAPAN).format(lastModifiedDate);
+
+            // 指定の日付(1日をミリ秒で計算)
+            long oneDayTime = 24L * 60L * 60L * 1000L;
+            long periodTime = oneDayTime * Math.abs(period);
+            Date designatedDate = new Date(System.currentTimeMillis() - periodTime);
+            String designatedDateStr = new SimpleDateFormat("yyyyMMdd", Locale.JAPAN).format(designatedDate);
+            if (period > 0) {
+                if (lastModifiedDateStr.compareTo(designatedDateStr) < 0) {
+                    return;
+                }
+            } else {
+                if (lastModifiedDateStr.compareTo(designatedDateStr) > 0) {
+                    return;
+                }
+            }
+        }
+        // 全ての条件に該当する場合リストに格納
+        set.add(file);
+    }
+    //endregion
+    //region 外部公開メソッド
+
+    /**
+     * インスタンスを生成後、続けて使用する場合は、このメソッドを
+     * 呼び出しクリアする必要がある。
+     */
+    public void clear() {
+        _treeSet.clear();
+    }
+
+    /**
+     * 指定したディレクトリ[directoryPath]から、
+     * 検索対象のファイル[fileName]を再帰的に検索し、該当する
+     * ファイルオブジェクトのリストを返します。
+     *
+     * @param directoryPath 検索対象のディレクトリを表すパス
+     * @param fileName      検索対象のファイル名
+     *                      ファイル名にはワイルドカード文字として*を指定可能
+     * @return 検索にマッチしたファイルオブジェクト
+     */
+    public File[] listFiles(String directoryPath, String fileName) {
+        // ワイルドカード文字として*を正規表現に変換
+        if (fileName != null) {
+            fileName = fileName.replace(".", "\\.");
+            fileName = fileName.replace("*", ".*");
+        }
+        return listFiles(directoryPath, fileName, TYPE.FILE, true, 0);
+    }
+
+    /**
+     * 指定したディレクトリ[directoryPath]から、正規表現として指定された
+     * 検索対象のファイル[fileNamePattern]を再帰的に検索し、
+     * 該当するファイルオブジェクトのリストを返します。
+     * <p>
+     * また、ファイルの更新日付が指定日数経過しているかどうかを検索条件に
+     * 指定する事ができます。
+     *
+     * @param directoryPath   検索対象のディレクトリを表すパス
+     * @param fileNamePattern 検索対象のファイル名[正規表現]
+     * @param type            該当するファイルオブジェクトの指定
+     * @param isRecursive     再帰的に検索する場合はtrue
+     * @param period          検索対象として、ファイルの更新日付が指定日数経過
+     *                        しているかどうかを設定可能
+     *                        0の場合は対象外
+     *                        1以上の場合、指定日数以降のファイルを検索対象とする
+     *                        -1以下の場合、指定日数以前のファイルを検索対象とする
+     * @return 検索にマッチしたファイルオブジェクト
+     */
+    private File[] listFiles(String directoryPath, String fileNamePattern, TYPE type, boolean isRecursive, int period) {
+
+        File dir = new File(directoryPath);
+        if (!dir.isDirectory()) {
+            throw new IllegalArgumentException("引数で指定されたパス[" + dir.getAbsolutePath() + "]はディレクトリではありません。");
+        }
+        File[] files = dir.listFiles();
+        // その出力
+        for (File file : files) {
+            addFile(type, fileNamePattern, _treeSet, file, period);
+            // 再帰的に検索&ディレクトリならば再帰的にリストに追加
+            if (isRecursive && file.isDirectory()) {
+                listFiles(file.getAbsolutePath(), fileNamePattern, type, isRecursive, period);
+            }
+        }
+        return _treeSet.toArray(new File[_treeSet.size()]);
+    }
+    //endregion
+}

+ 64 - 0
app/src/main/java/jp/co/ecosysnetwork/universal/EsnQueue.java

@@ -0,0 +1,64 @@
+package jp.co.ecosysnetwork.universal;
+
+import java.util.LinkedList;
+
+/**
+ * キュークラス
+ * @param <E> レコードのクラス
+ */
+public class EsnQueue<E> {
+    //region メンバ変数
+    /**
+     * キューリスト
+     */
+    private LinkedList<E> _queues;
+    /**
+     * 最大キューサイズ
+     */
+    private int _maxQueueSize;
+    /**
+     * 排他用オブジェクト
+     */
+    private final Object _lockObj = new Object();
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     * @param maxQueueSize 最大キューサイズ
+     */
+    public EsnQueue(int maxQueueSize) {
+        _queues = new LinkedList<E>();
+        _maxQueueSize = maxQueueSize;
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * キューに追加
+     * @param obj 追加レコード
+     * @return ture:成功 false:最大数オーバー
+     */
+    public boolean put(E obj) {
+        synchronized (_lockObj) {
+            if (_queues.size() < _maxQueueSize) {
+                _queues.addFirst(obj);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+    /**
+     * キューから抜き出し
+     * @return レコード。レコードがない場合、null。
+     */
+    public E get() {
+        synchronized (_lockObj) {
+            if (_queues.size() > 0) {
+                return _queues.removeLast();
+            } else {
+                return null;
+            }
+        }
+    }
+    //endregion
+}

+ 305 - 0
app/src/main/java/jp/co/ecosysnetwork/universal/EsnUtility.java

@@ -0,0 +1,305 @@
+package jp.co.ecosysnetwork.universal;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.text.InputFilter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * ECOSYSNETWORK K.K.  共通ユーティリティクラス
+ */
+public class EsnUtility {
+    //region 定数
+    /**
+     * 大文字アルファベット
+      */
+    private static final String[] ALPHABETS = new String[]{
+            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
+    };
+    /**
+     * ファイル名として使用できない文字
+     */
+    public static final char[] PROHIBITED_SYMBOLS = {
+            '\\', '/', ':', '*', '?', '\'', '\"', '<', '>'
+    };
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * 指定された前階層の実行中クラス名を取得します。
+     *
+     * @param preIdx 指定された前階層
+     * @return クラス名
+     */
+    public static String getClassName(int preIdx) {
+        return Thread.currentThread().getStackTrace()[preIdx].getClassName();
+    }
+    /**
+     * 実行中のクラス名を取得します。
+     *
+     * @return クラス名
+     */
+    public static String getClassName() {
+        return getClassName(3);
+    }
+    /**
+     * 指定された前階層の実行中メソッド名を取得します。
+     *
+     * @param preIdx 指定された前階層
+     * @return メソッド名
+     */
+    public static String getMethodName(int preIdx) {
+        return Thread.currentThread().getStackTrace()[preIdx].getMethodName();
+    }
+    /**
+     * 実行中のメソッド名を取得します。
+     *
+     * @return メソッド名
+     */
+    public static String getMethodName() {
+        return getMethodName(3);
+    }
+    /**
+     * ファイル名から拡張子を返します。
+     *
+     * @param fileName ファイル名
+     * @return ファイルの拡張子
+     */
+    public static String getFileSuffix(String fileName) {
+        if (fileName == null) {
+            return null;
+        }
+        int point = fileName.lastIndexOf(".");
+        if (point != -1) {
+            return fileName.substring(point + 1);
+        }
+        return fileName;
+    }
+    /**
+     * ファイル名から拡張子を取り除いた名前を返します。
+     *
+     * @param fileName ファイル名
+     * @return ファイル名
+     */
+    public static String getFilePreffix(String fileName) {
+        if (fileName == null) {
+            return null;
+        }
+        int point = fileName.lastIndexOf(".");
+        if (point != -1) {
+            return fileName.substring(0, point);
+        }
+        return fileName;
+    }
+    /**
+     * バージョンコードを取得する
+     *
+     * @param context コンテキスト
+     * @return バージョンコード
+     */
+    public static int getVersionCode(Context context) {
+        PackageManager pm = context.getPackageManager();
+        int versionCode = 0;
+        try {
+            PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
+            versionCode = packageInfo.versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return versionCode;
+    }
+    /**
+     * バージョン名を取得する
+     *
+     * @param context コンテキスト
+     * @return バージョン名
+     */
+    public static String getVersionName(Context context) {
+        PackageManager pm = context.getPackageManager();
+        String versionName = "";
+        try {
+            PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
+            versionName = packageInfo.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return versionName;
+    }
+    /**
+     * 配列から値を検索し、発見したインデックスを返す
+     * @param array 配列
+     * @param find 検索値
+     * @return 発見したインデックス。-1:見つからない
+     */
+    public static int indexOf(int[] array, int find) {
+        int ret = -1;
+        for (int i=0; i<array.length; i++) {
+            if (array[i] == find) {
+                ret = i;
+                break;
+            }
+        }
+        return ret;
+    }
+    /**
+     * 指定パスのBMPファイルを読込BITMAP形式で返す
+     * @param filePath ファイルパス
+     * @return BITMAPデータ
+     */
+    public static Bitmap readBitmapFile(String filePath) {
+        Bitmap ret = null;
+        try {
+            File srcFile = new File(filePath);
+            if (srcFile.exists()) {
+                FileInputStream fis = new FileInputStream(srcFile);
+                ret = BitmapFactory.decodeStream(fis);
+            }
+        }
+        catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return ret;
+    }
+    /**
+     * 文字列が数値のみであるか判定
+     * @param target 文字列
+     * @return true:数値である false:数値でない
+     */
+    public static boolean isNumeric(String target) {
+        boolean ret = false;
+        for (int i = 0; i < target.length(); i++) {
+            ret = Character.isDigit(target.charAt(i));
+            if (!ret) {
+                break;
+            }
+        }
+        return ret;
+    }
+    /**
+     * メールアドレスとして正しい文字列か判定
+     * @param address メールアドレス
+     * @return true:正しい false:正しくない
+     */
+    public static boolean isOkMailAddress(String address) {
+        boolean ret;
+        String aText = "[\\w!#%&'/=~`\\*\\+\\?\\{\\}\\^\\$\\-\\|]";
+        String dotAtom = aText + "+" + "(\\." + aText + "+)*";
+        String regularExpression = "^" + dotAtom + "@" + dotAtom + "$";
+        Pattern pattern = Pattern.compile(regularExpression);
+        Matcher matcher = pattern.matcher(address);
+        return matcher.find();
+    }
+    /**
+     * ファイル名として正しい文字列か判定
+     * @param fileName ファイル名
+     * @return true:正しい false:正しくない
+     */
+    public static boolean isOkFileName(String fileName) {
+        if (fileName.length() == 0) {
+            return false;
+        }
+        boolean ret = true;
+        for (int i = 0; i < fileName.length(); i++) {
+            for (char c : PROHIBITED_SYMBOLS) {
+                if (fileName.charAt(i) == c) {
+                    ret = false;
+                    break;
+                }
+            }
+            if (!ret) {
+                break;
+            }
+        }
+        return ret;
+    }
+    /**
+     * リスト要素の入替
+     * @param list リスト
+     * @param index1 入替えるインデックス1
+     * @param index2 入替えるインデックス2
+     * @param <T> リストの型
+     */
+    public static<T> void swap(List<T> list, int index1, int index2) {
+        T tmp=list.get(index1);
+        list.set(index1,list.get(index2));
+        list.set(index2, tmp);
+    }
+    /**
+     * 緯度、経度から2点間の距離を算出
+     * @param lat1 緯度1
+     * @param lon1 経度1
+     * @param lat2 緯度2
+     * @param lon2 経度2
+     * @return 2点間の距離(単位:メートル)
+     */
+    public static double CalcDistanceFromLocation(double lat1, double lon1, double lat2, double lon2) {
+        double ret = 0;
+        if ((lat1 > 0) && (lon1 > 0) && (lat2 > 0) && (lon2 > 0)) {
+            final double M_PI = 3.14159265358979;
+            // 始点緯度をラジアンに変換
+            double dSirad = lat1 * M_PI / 180;
+            // 始点経度をラジアンに変換
+            double dSkrad = lon1 * M_PI / 180;
+
+            // 終点緯度をラジアンに変換
+            double dSyirad = lat2 * M_PI / 180;
+            // 終点経度をラジアンに変換
+            double dSykrad = lon2 * M_PI / 180;
+
+            //2点間の平均緯度を計算
+            double dAveirad = (dSirad + dSyirad) / 2;
+
+            //2点間の緯度差を計算
+            double dDeffirad = dSirad - dSyirad;
+
+            //2点間の経度差を計算
+            double dDeffkrad = dSkrad - dSykrad;
+
+            //GRS80楕円体 (2000年系)
+            //子午線曲率半径を計算
+            double dTemp = 1 - 0.006694 * (Math.sin(dAveirad) * Math.sin(dAveirad));
+            double dDmrad = 6335439 / Math.sqrt(dTemp * dTemp * dTemp);
+            //卯酉線曲率半径を取得
+            double dDvrad = 6378137 / Math.sqrt(dTemp);
+
+            //ヒュベニの距離計算式
+            double dT1 = dDmrad * dDeffirad;
+            double dT2 = dDvrad * Math.cos(dAveirad) * dDeffkrad;
+            ret = Math.sqrt(dT1 * dT1 + dT2 * dT2);
+        }
+        return ret;
+    }
+    /**
+     * 緯度、経度から2点間の角度を算出
+     * @param lat1 緯度1
+     * @param lon1 経度1
+     * @param lat2 緯度2
+     * @param lon2 経度2
+     * @return 2点間の角度(0~360度)
+     */
+    public static double CalcAngleFromLocation(double lat1, double lon1, double lat2, double lon2) {
+        double ret = 0;
+        if ((lat1 > 0) && (lon1 > 0) && (lat2 > 0) && (lon2 > 0)) {
+            double radLat1 = Math.toRadians(lat2);
+            double radLon1 = Math.toRadians(lon2);
+            double radLat2 = Math.toRadians(lat1);
+            double radLon2 = Math.toRadians(lon1);
+            double Y = Math.sin(radLon2 - radLon1) * Math.cos(radLat2);
+            double X = Math.cos(radLat1) * Math.sin(radLat2) - Math.sin(radLat1) * Math.cos(radLat2) * Math.cos(radLon2 - radLon1);
+            double deg = Math.toDegrees(Math.atan2(Y, X));
+            double angle = (deg + 360) % 360;
+
+            ret = (Math.abs(angle) + ((double)1 / 7200));
+        }
+        return ret;
+    }
+    //endregion
+}

+ 129 - 0
app/src/main/java/jp/co/ecosysnetwork/universal/EsnXmlParser.java

@@ -0,0 +1,129 @@
+package jp.co.ecosysnetwork.universal;
+
+import android.content.SharedPreferences;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * XMLパーサ
+ */
+public class EsnXmlParser {
+    //region クラス
+    /**
+     * 解析項目
+     */
+    public class ParseItem {
+        /**
+         * タグ
+         */
+        public String tag;
+        /**
+         * 値
+         */
+        public String value;
+        //region 生成と廃棄
+        /**
+         * コンストラクタ
+         */
+        public ParseItem() {
+            this.tag = "";
+            this.value = "";
+        }
+        /**
+         * コンストラクタ
+         * @param tag タグ
+         * @param value 値
+         */
+        public ParseItem(String tag, String value) {
+            this.tag = tag;
+            this.value = value;
+        }
+        //endregion
+    }
+    //endregion
+    //region メンバ変数
+    /**
+     * XMLパーサ
+     */
+    private XmlPullParser _parser;
+    /**
+     * 解析項目リスト
+     */
+    private List<ParseItem> _items;
+    //endregion
+    //region 生成と廃棄
+    /**
+     * コンストラクタ
+     * @param parser XMLパーサ
+     */
+    public EsnXmlParser(XmlPullParser parser) {
+        _parser = parser;
+        _items = new ArrayList<>();
+    }
+    //endregion
+    //region 外部公開メソッド
+    /**
+     * 解析実行
+     * @return エラーメッセージ
+     */
+    public String tryParse() {
+        String ret = null;
+        _items = new ArrayList<>();
+        try {
+            int et = _parser.getEventType();
+            boolean cmplt = false;
+            for (int i=0; i<10000; i++) {     // 最大回数に保険をかける
+                // 解析終了すると抜ける
+                if (et == XmlPullParser.END_DOCUMENT) {
+                    cmplt = true;
+                    break;
+                }
+                if (et == XmlPullParser.START_TAG) {
+                    String tag = _parser.getName();
+                    String value = "";
+                    boolean cmplt2 = false;
+                    for (int j=0; j<100; j++) { // 最大回数に保険をかける
+                        if (et == XmlPullParser.TEXT) {
+                            value = _parser.getText();
+                            cmplt2 = true;
+                            break;
+                        }
+                        et = _parser.next();
+                    }
+                    if (cmplt2) {
+                        _items.add(new ParseItem(tag, value));
+                        et = _parser.next();
+                    }
+                    else {
+                        ret = "タグに該当する値が存在しない タグ=" + tag;
+                        return ret;
+                    }
+                } else if (et == XmlPullParser.END_TAG) {
+                    et = _parser.next();
+                } else if (et == XmlPullParser.START_DOCUMENT) {
+                    et = _parser.next();
+                } else {
+                    et = _parser.next();
+                }
+            }
+            if (!cmplt) {
+                ret = "終了タグが存在しない";
+            }
+        }
+        catch (Exception e) {
+            ret = "例外エラー " + e.toString();
+        }
+        return ret;
+    }
+    /**
+     * 解析項目リストを取得
+     * @return 解析項目リスト
+     */
+    public List<ParseItem> getParseItems() {
+        return _items;
+    }
+    //endregion
+}

+ 28 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:pathData="M71.2,76.75A24.32,24.32 0,1 1,74 44.64"
+        android:strokeWidth="7"
+        android:fillColor="#fff"
+        android:strokeColor="#51b6d1"
+        android:strokeLineCap="round"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M47.01,66.31m-12.35,0a12.35,12.35 0,1 1,24.7 0a12.35,12.35 0,1 1,-24.7 0"
+        android:fillColor="#51b6d1"
+        android:strokeColor="#51b6d1"/>
+    <path
+        android:strokeWidth="1"
+        android:pathData="M55.91,63.44a6.86,6.86 0,0 1,-1 3.52,33 33,0 0,1 -4.21,5.24l-3.73,4 -3.74,-4A33.22,33.22 0,0 1,39 67a6.84,6.84 0,0 1,-1 -3.51,4.72 4.72,0 0,1 0.78,-2.73A4.31,4.31 0,0 1,41.36 59a4.4,4.4 0,0 1,1.13 -0.16c1.6,0 3.1,0.88 4.5,2.62 1.42,-1.74 2.93,-2.62 4.52,-2.62a4.52,4.52 0,0 1,1.12 0.16,4.19 4.19,0 0,1 2.5,1.73A4.72,4.72 0,0 1,55.91 63.44Z"
+        android:fillColor="#e38397"
+        android:strokeColor="#fff"/>
+    <path
+        android:pathData="M70.29,23.06a7.18,7.18 0,0 0,-7.23 1.65c-5.52,5.5 -5.2,22 0.26,28.14 2.64,2.95 7.58,4.72 10,1.07 1.79,-2.74 1.27,-7.25 1.13,-8.34"
+        android:strokeWidth="7"
+        android:fillColor="#00000000"
+        android:strokeColor="#51b6d1"
+        android:strokeLineCap="round"/>
+</vector>

+ 68 - 0
app/src/main/res/drawable/bg_button.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- 有効ボタンが押されたときの定義 -->
+    <item android:state_pressed="true" android:state_enabled="true">
+        <shape
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:shape="rectangle">
+            <!-- 枠線 -->
+            <stroke
+                android:width="@dimen/buttonMiddleStrokeWidth"
+                android:color="@color/buttonStorokeColor" />
+
+            <!-- 角を丸める -->
+            <corners
+                android:radius="@dimen/buttonMiddleRadius"
+            />
+
+            <!-- この形状の色 -->
+            <solid
+                android:color="@color/buttonPressColor"
+                />
+        </shape>
+    </item>
+
+    <!-- 有効ボタンが押されていないときの定義 -->
+    <item android:state_pressed="false"  android:state_enabled="true">
+        <shape
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:shape="rectangle">
+            <!-- 枠線 -->
+            <stroke
+                android:width="@dimen/buttonMiddleStrokeWidth"
+                android:color="@color/buttonStorokeColor" />
+
+            <!-- 角を丸める -->
+            <corners
+                android:radius="@dimen/buttonMiddleRadius"
+            />
+
+            <!-- この形状の色 -->
+            <solid
+                android:color="@color/buttonNormalColor"
+                />
+        </shape>
+    </item>
+
+    <!-- 無効ボタンの定義 -->
+    <item android:state_enabled="false">
+        <shape
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:shape="rectangle">
+            <!-- 枠線 -->
+            <stroke
+                android:width="@dimen/buttonMiddleStrokeWidth"
+                android:color="@color/buttonStorokeColor" />
+
+            <!-- 角を丸める -->
+            <corners
+                android:radius="@dimen/buttonMiddleRadius"
+                />
+
+            <!-- この形状の色 -->
+            <solid
+                android:color="@color/buttonDisableColor"
+                />
+        </shape>
+    </item>
+</selector>

+ 13 - 0
app/src/main/res/drawable/bg_data.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <!-- データ背景色-->
+    <solid
+        android:color="@color/dataColor"/>
+
+    <!-- データ枠線-->
+    <stroke
+        android:width="@dimen/dataStrokeWidth"
+        android:color="@color/dataStorokeColor" />
+</shape>

+ 13 - 0
app/src/main/res/drawable/bg_edittext.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <!-- エディットテキスト背景色-->
+    <solid
+        android:color="@color/editColor"/>
+
+    <!-- エディットテキスト枠線-->
+    <stroke
+        android:width="@dimen/editStrokeWidth"
+        android:color="@color/editStorokeColor" />
+</shape>

+ 13 - 0
app/src/main/res/drawable/bg_frame.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <!-- フレーム背景色-->
+    <solid
+        android:color="@color/frameColor"/>
+
+    <!-- フレーム枠線-->
+    <stroke
+        android:width="@dimen/frameStrokeWidth"
+        android:color="@color/frameStorokeColor" />
+</shape>

+ 13 - 0
app/src/main/res/drawable/bg_frame_warn.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <!-- フレーム背景色-->
+    <solid
+        android:color="@color/frameWarnColor"/>
+
+    <!-- フレーム枠線-->
+    <stroke
+        android:width="@dimen/frameStrokeWidth"
+        android:color="@color/frameWarnStorokeColor" />
+</shape>

+ 5 - 0
app/src/main/res/drawable/ic_add_small_normal.xml

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#008000"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>

+ 6 - 0
app/src/main/res/drawable/ic_back_activity.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+        android:drawable="@drawable/ic_back_activity_press" /> <!-- pressed -->
+    <item android:drawable="@drawable/ic_back_activity_normal" /> <!-- default -->
+</selector>

+ 11 - 0
app/src/main/res/drawable/ic_back_activity_normal.xml

@@ -0,0 +1,11 @@
+<vector
+    android:height="@dimen/activityHeaderIconSize"
+    android:tint="@color/activityHeaderIconNormalColor"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="@dimen/activityHeaderIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>

+ 11 - 0
app/src/main/res/drawable/ic_back_activity_press.xml

@@ -0,0 +1,11 @@
+<vector
+    android:height="@dimen/activityHeaderIconSize"
+    android:tint="@color/activityHeaderIconPressColor"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="@dimen/activityHeaderIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_bas_stop.xml

@@ -0,0 +1,10 @@
+<vector
+    android:height="24dp"
+    android:tint="@color/buttonIconNormalColor"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:width="24dp"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white"
+        android:pathData="M13,16.12c3.47,-0.41 6.17,-3.36 6.17,-6.95 0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7c0,3.47 2.52,6.34 5.83,6.89V20H5v2h14v-2h-6v-3.88z"/>
+</vector>

+ 11 - 0
app/src/main/res/drawable/ic_cancel_normal.xml

@@ -0,0 +1,11 @@
+<vector
+    android:height="@dimen/buttonMiddleIconSize"
+    android:tint="#FF7E71"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="@dimen/buttonMiddleIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>

+ 5 - 0
app/src/main/res/drawable/ic_clear_small_normal.xml

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FF7E71"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>

+ 11 - 0
app/src/main/res/drawable/ic_close_normal.xml

@@ -0,0 +1,11 @@
+<vector
+    android:height="@dimen/buttonMiddleIconSize"
+    android:tint="#FF7E71"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="@dimen/buttonMiddleIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5 - 0
app/src/main/res/drawable/ic_comm_disable.xml


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5 - 0
app/src/main/res/drawable/ic_comm_enable.xml


+ 10 - 0
app/src/main/res/drawable/ic_import.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/buttonMiddleIconSize"
+        android:height="@dimen/buttonMiddleIconSize"
+        android:tint="@color/buttonIconNormalColor"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"
+        android:fillColor="#010101"/>
+</vector>

+ 36 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,36 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:pathData="M10,0L98,0A10,10 0,0 1,108 10L108,98A10,10 0,0 1,98 108L10,108A10,10 0,0 1,0 98L0,10A10,10 0,0 1,10 0z"
+        android:fillColor="#80ccdd"/>
+    <path
+        android:pathData="M54,54m-41.27,0a41.27,41.27 0,1 1,82.54 0a41.27,41.27 0,1 1,-82.54 0"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M16.86,18.6a34.72,34.72 0,0 1,-4.19 0.28,10.23 10.23,0 0,1 -4.29,-0.62A2,2 0,0 1,7.2 16.42a3.48,3.48 0,0 1,1.62 -2.76l1.17,0.88q-1.11,1.05 -1.11,1.74a0.83,0.83 0,0 0,0.07 0.36,0.77 0.77,0 0,0 0.34,0.33 2.89,2.89 0,0 0,1 0.27,13 13,0 0,0 1.83,0.11h0.43A27.91,27.91 0,0 0,16.61 17ZM16.43,11.16a4.86,4.86 0,0 0,-0.85 0.22,7.32 7.32,0 0,0 -0.76,0.29c-0.2,0.09 -0.94,0.44 -2.22,1.07l-0.72,-1 1.36,-0.64c-1.15,0 -2.89,0.06 -5.21,0.14L8,9.76q1.66,-0.06 6.34,-0.06h1.55Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M28.86,18.6a34.72,34.72 0,0 1,-4.19 0.28,10.23 10.23,0 0,1 -4.29,-0.62 2,2 0,0 1,-1.18 -1.84,3.48 3.48,0 0,1 1.62,-2.76l1.17,0.88q-1.11,1.05 -1.11,1.74a0.83,0.83 0,0 0,0.07 0.36,0.77 0.77,0 0,0 0.34,0.33 2.89,2.89 0,0 0,1 0.27,13 13,0 0,0 1.83,0.11h0.43A27.91,27.91 0,0 0,28.61 17ZM28.43,11.16a4.86,4.86 0,0 0,-0.85 0.22,7.32 7.32,0 0,0 -0.76,0.29c-0.2,0.09 -0.94,0.44 -2.22,1.07l-0.72,-1 1.36,-0.64c-1.15,0 -2.89,0.06 -5.21,0.14l0,-1.45q1.67,-0.06 6.34,-0.06h1.55Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M40.72,18.75h-9.5L31.22,9.81h9.5ZM38.89,17.25v-6L33,11.25v6Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M50.67,12.46A7,7 0,0 1,48.9 17,8.3 8.3,0 0,1 44,19.09l-0.41,-1.51a7.27,7.27 0,0 0,3.92 -1.64,5.47 5.47,0 0,0 1.41,-3.48H45.81a9.22,9.22 0,0 1,-2.92 2.67l-1,-1.21a8.51,8.51 0,0 0,3.68 -5.39l1.74,0.2a13.24,13.24 0,0 1,-0.68 2.19h7v1.54Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M65.41,14.82H54.64V13.27H65.41Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M72.4,13.4l-0.8,1.48c-0.87,-0.38 -1.58,-0.67 -2.14,-0.86s-1.49,-0.47 -2.8,-0.84l0.67,-1.38c0.82,0.17 1.56,0.36 2.23,0.57S71.18,12.93 72.4,13.4ZM77.64,12.94a10.6,10.6 0,0 1,-4 4.25,14.25 14.25,0 0,1 -6,1.79l-0.38,-1.57A14.15,14.15 0,0 0,72.47 16a9.82,9.82 0,0 0,3.71 -3.75ZM73.54,10.53L72.71,12c-0.43,-0.19 -0.87,-0.38 -1.32,-0.56s-0.82,-0.33 -1.11,-0.42l-2.46,-0.79 0.69,-1.35A35.41,35.41 0,0 1,73.54 10.53Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M87.64,18.85H79.5v-1.3h6.59V15.32H79.72V14.06h6.37V12.21h-6.4V11h8Z"
+        android:fillColor="#2872b5"/>
+    <path
+        android:pathData="M96.88,11.54 L95.81,13c-0.58,-0.37 -1.09,-0.68 -1.54,-0.94s-1.46,-0.77 -3,-1.57l0.92,-1.34A38.37,38.37 0,0 1,96.88 11.54ZM101.63,13a10.72,10.72 0,0 1,-3.88 4.19A14.44,14.44 0,0 1,91.68 19l-0.4,-1.65a16.1,16.1 0,0 0,3.34 -0.6A8.77,8.77 0,0 0,97 15.58a11.48,11.48 0,0 0,1.71 -1.41A10.91,10.91 0,0 0,100.22 12Z"
+        android:fillColor="#2872b5"/>
+</vector>

+ 5 - 0
app/src/main/res/drawable/ic_location_disable.xml

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FF7070"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>

+ 5 - 0
app/src/main/res/drawable/ic_location_enable.xml

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#70FF70"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
+</vector>

+ 11 - 0
app/src/main/res/drawable/ic_next_activity_normal.xml

@@ -0,0 +1,11 @@
+<vector
+    android:height="@dimen/buttonMiddleIconSize"
+    android:tint="@color/buttonIconNormalColor"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="@dimen/buttonMiddleIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
+</vector>

+ 11 - 0
app/src/main/res/drawable/ic_ok_normal.xml

@@ -0,0 +1,11 @@
+<vector
+    android:height="@dimen/buttonMiddleIconSize"
+    android:tint="#008000"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="@dimen/buttonMiddleIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_save.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/buttonMiddleTextSize"
+        android:height="@dimen/buttonMiddleIconSize"
+        android:tint="@color/buttonIconNormalColor"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16,13h-3V3h-2v10H8l4,4 4,-4zM4,19v2h16v-2H4z"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_send_mail_listview.xml

@@ -0,0 +1,9 @@
+<vector android:height="@dimen/listViewIconSize"
+    android:tint="@color/buttonIconNormalColor"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:width="@dimen/listViewIconSize"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white"
+        android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8l8,5 8,-5v10zM12,11L4,6h16l-8,5z"/>
+</vector>

+ 199 - 0
app/src/main/res/layout/activity_course_settings.xml

@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activity.CourseSettingsActivity">
+
+    <!-- アクティビティヘッダ -->
+    <LinearLayout
+        android:id="@+id/lnlHeader"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/activityHeaderHeight"
+        android:background="@color/activityHeaderColor"
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <!--アクティビティヘッダ(左)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="0.5"
+            android:layout_height="match_parent">
+
+            <!-- 戻るアイコン -->
+            <Button
+                android:id="@+id/imbBack"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableStart="@drawable/ic_back_activity"
+                android:text="@string/descBack"
+                android:textSize="@dimen/buttonSmallTextSize"
+                android:textColor="@color/activityTitleTextColor"
+                android:background="@null"
+                android:layout_margin="@dimen/activityHeaderIconMargin"
+                android:layout_gravity="center_vertical"
+                />
+        </LinearLayout>
+
+        <!--アクティビティヘッダ(中央)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="match_parent">
+
+            <!--タイトル-->
+            <TextView
+                android:id="@+id/txvActivityTitle"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                android:gravity="center"
+                android:text="@string/courseSettings"
+                android:textColor="@color/activityTitleTextColor"
+                android:textSize="@dimen/activityTitleTextSize" />
+        </LinearLayout>
+
+        <!--アクティビティヘッダ(右)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="0.5"
+            android:layout_height="match_parent"
+            android:gravity="end">
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- アクティビティボディ全体の水平リニアレイアウト-->
+    <LinearLayout
+        android:id="@+id/lnlBody"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:baselineAligned="false"
+        android:orientation="horizontal"
+        app:layout_constraintBottom_toTopOf="@+id/lnlFooter"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lnlHeader">
+
+        <!-- コース設定垂直リニアレイアウト-->
+        <LinearLayout
+            android:id="@+id/lnlCourseSettingsList"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_margin="@dimen/frameMargin"
+            android:background="@drawable/bg_frame"
+            android:baselineAligned="false"
+            android:orientation="vertical"
+            android:padding="@dimen/framePadding">
+
+            <!-- コース設定フレームタイトル-->
+            <TextView
+                android:id="@+id/txvCourseSettingsFrameTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/courseSettings"
+                android:textColor="@color/frameTitleTextColor"
+                android:textSize="@dimen/frameTitleTextSize"
+                android:textStyle="bold" />
+
+            <!-- コース設定リストビュー -->
+
+            <!-- コース選択リストビュー -->
+            <Spinner
+                android:id="@+id/spnCourseSelect"
+                android:layout_width="match_parent"
+                android:layout_height="50dp"
+                android:layout_margin="@dimen/itemMargin"
+                android:padding="@dimen/framePadding"
+                android:background="@drawable/bg_frame"
+                android:textColor="@color/frameTitleTextColor"
+                android:textSize="@dimen/frameTitleTextSize" />
+
+        </LinearLayout>
+
+        <!-- コース選択垂直リニアレイアウト-->
+    </LinearLayout>
+
+    <!-- アクティビティフッタ -->
+    <LinearLayout
+        android:id="@+id/lnlFooter"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/activityFooterPadding"
+        android:paddingRight="@dimen/activityFooterPadding"
+        android:paddingBottom="@dimen/activityFooterPadding"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+
+        <!-- フッタボタン垂直リニアレイアウト-->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <!-- プログレスバー -->
+            <ProgressBar
+                android:id="@+id/prgUpdateCourse"
+                style="?android:attr/progressBarStyleHorizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:indeterminate="true"
+                android:max="100"
+                android:backgroundTint="@color/prgBarBackColor"
+                android:indeterminateTint="@color/prgBarInterminateColor"
+                android:layout_marginTop="1dp"
+                android:visibility="gone"/>
+
+            <!-- フッタボタン水平レイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlFooterButton"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:padding="@dimen/activityFooterPadding"
+                android:orientation="horizontal">
+
+                <!-- 保存ボタン-->
+                <Button
+                    android:id="@+id/btnSave"
+                    android:layout_width="@dimen/buttonSmallWidth"
+                    android:layout_height="@dimen/buttonSmallHeight"
+                    android:background="@drawable/bg_button"
+                    android:drawableStart="@drawable/ic_save"
+                    android:gravity="center"
+                    android:paddingLeft="@dimen/buttonSmallPadding"
+                    android:paddingRight="@dimen/buttonSmallPadding"
+                    android:text="@string/buttonSave"
+                    android:textColor="@color/buttonTextColor"
+                    android:textSize="@dimen/buttonSmallTextSize" />
+
+                <!-- ダミービュー -->
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:visibility="invisible" />
+
+                <!-- コース更新ボタン-->
+                <Button
+                    android:id="@+id/btnUpdateCourse"
+                    android:layout_width="@dimen/buttonSmallWidth"
+                    android:layout_height="@dimen/buttonSmallHeight"
+                    android:background="@drawable/bg_button"
+                    android:drawableStart="@drawable/ic_import"
+                    android:gravity="center"
+                    android:paddingLeft="@dimen/buttonSmallPadding"
+                    android:paddingRight="@dimen/buttonSmallPadding"
+                    android:text="@string/buttonUpdateCourse"
+                    android:textColor="@color/buttonTextColor"
+                    android:textSize="@dimen/buttonSmallTextSize" />
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 194 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activity.MainActivity">
+
+    <!-- アクティビティヘッダ -->
+    <LinearLayout
+        android:id="@+id/lnlHeader"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/activityHeaderHeight"
+        android:background="@color/activityHeaderColor"
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+
+        <!--アクティビティヘッダ(中央)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="match_parent"/>
+
+        <!--アクティビティヘッダ(右)-->
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="end">
+
+            <!-- サーバ通信状態アイコン -->
+            <ImageView
+                android:id="@+id/imvCommState"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/activityHeaderIconMargin"
+                android:background="@null"
+                android:contentDescription="@string/descCommIcon"
+                android:visibility="invisible"/>
+
+            <!-- 位置情報取得状態アイコン -->
+            <ImageView
+                android:id="@+id/imvLocationState"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/activityHeaderIconMargin"
+                android:background="@null"
+                android:contentDescription="@string/descLocationIcon"
+                android:visibility="invisible"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- アクティビティボディ全体の垂直リニアレイアウト-->
+    <LinearLayout
+        android:id="@+id/lnlBody"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:baselineAligned="false"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toTopOf="@+id/lnlFooter"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lnlHeader">
+
+        <!-- 通過ポイント垂直リニアレイアウト-->
+        <LinearLayout
+            android:id="@+id/lnlCoursePointsList"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:layout_margin="@dimen/frameMargin"
+            android:background="@drawable/bg_frame"
+            android:baselineAligned="false"
+            android:orientation="vertical"
+            android:padding="@dimen/framePadding">
+
+            <TextView
+                android:id="@+id/txvSelectedCourseNameTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/courseSelected"
+                android:textColor="@color/frameTitleTextColor"
+                android:textSize="@dimen/frameTitleTextSize"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/txvSelectedCourseName"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/itemMargin"
+                android:textColor="@color/titleTextColor"
+                android:textSize="@dimen/nowDriveStatusTextSize"/>
+
+            <View
+                android:id="@+id/view"
+                android:layout_width="match_parent"
+                android:layout_height="33dp" />
+
+            <TextView
+                android:id="@+id/txvNowDriveStatusTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/nowDriveStatus"
+                android:textColor="@color/frameTitleTextColor"
+                android:textSize="@dimen/frameTitleTextSize"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/txvOperationStatus"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/itemMargin"
+                android:textColor="@color/titleTextColor"
+                android:textSize="@dimen/nowDriveStatusTextSize"
+                android:gravity="center_horizontal"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- アクティビティフッタ -->
+    <LinearLayout
+        android:id="@+id/lnlFooter"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/activityFooterPadding"
+        android:paddingRight="@dimen/activityFooterPadding"
+        android:paddingBottom="@dimen/activityFooterPadding"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+
+        <!-- フッタボタン垂直リニアレイアウト-->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <!-- フッタボタン水平レイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlFooterButton"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:padding="@dimen/activityFooterPadding"
+                android:orientation="horizontal">
+
+                <!-- プログレスバー -->
+                <ProgressBar
+                    android:id="@+id/prgStartAndEndCourse"
+                    style="?android:attr/progressBarStyleHorizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:indeterminate="true"
+                    android:max="100"
+                    android:backgroundTint="@color/prgBarBackColor"
+                    android:indeterminateTint="@color/prgBarInterminateColor"
+                    android:layout_marginTop="1dp"
+                    android:visibility="gone"/>
+
+                <!-- コース変更ボタン-->
+                <Button
+                    android:id="@+id/btnStartDrive"
+                    android:layout_width="@dimen/buttonSmallWidth"
+                    android:layout_height="@dimen/buttonSmallHeight"
+                    android:background="@drawable/bg_button"
+                    android:gravity="center"
+                    android:text="@string/buttonStartDrive"
+                    android:textColor="@color/buttonTextColor"
+                    android:textSize="@dimen/buttonSmallTextSize" />
+
+                <!-- ダミービュー -->
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:visibility="invisible" />
+
+                <!-- 終了ボタン-->
+                <Button
+                    android:id="@+id/btnExit"
+                    android:layout_width="@dimen/buttonSmallWidth"
+                    android:layout_height="@dimen/buttonSmallHeight"
+                    android:background="@drawable/bg_button"
+                    android:gravity="center"
+                    android:text="@string/buttonEndDrive"
+                    android:textColor="@color/buttonTextColor"
+                    android:textSize="@dimen/buttonSmallTextSize" />
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 131 - 0
app/src/main/res/layout/activity_splash.xml

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activity.SplashActivity">
+
+    <!-- スプラッシュ画面はデザインの統一性を考えないため、他で使用する値以外は直接値を設定する。 -->
+
+    <!-- アプリケーション名 -->
+    <TextView
+        android:id="@+id/txvAppName"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/app_name"
+        android:textColor="@color/colorPrimary"
+        android:textSize="40sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- 開始ボタン-->
+    <Button
+        android:id="@+id/btnStart"
+        android:layout_width="@dimen/buttonMiddleWidth"
+        android:layout_height="@dimen/buttonMiddleHeight"
+        android:background="@drawable/bg_button"
+        android:drawableStart="@drawable/ic_next_activity_normal"
+        android:gravity="center"
+        android:paddingLeft="@dimen/buttonMiddlePadding"
+        android:paddingRight="@dimen/buttonMiddlePadding"
+        android:layout_marginBottom="120dp"
+        android:text="@string/buttonStart"
+        android:textColor="@color/buttonTextColor"
+        android:textSize="@dimen/buttonMiddleTextSize"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="@id/lnlSettings"
+        />
+
+    <!-- 設定ボタン 水平リニアレイアウト -->
+    <LinearLayout
+        android:id="@+id/lnlSettings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="80dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="@id/txvVersion"
+        >
+        <!-- ダミービュー -->
+        <View
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <!-- コース設定ボタン-->
+        <Button
+            android:id="@+id/btnCourseSettings"
+            android:layout_width="180dp"
+            android:layout_height="@dimen/buttonSmallHeight"
+            android:background="@drawable/bg_button"
+            android:drawableStart="@drawable/ic_next_activity_normal"
+            android:gravity="center"
+            android:paddingLeft="@dimen/buttonSmallPadding"
+            android:paddingRight="@dimen/buttonSmallPadding"
+            android:text="@string/buttonCourseSettings"
+            android:textColor="@color/buttonTextColor"
+            android:textSize="@dimen/buttonSmallTextSize"
+            />
+
+        <!-- ダミービュー -->
+        <View
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <!-- システム設定ボタン-->
+        <Button
+            android:id="@+id/btnSystemSettings"
+            android:layout_width="180dp"
+            android:layout_height="@dimen/buttonSmallHeight"
+            android:background="@drawable/bg_button"
+            android:drawableStart="@drawable/ic_next_activity_normal"
+            android:gravity="center"
+            android:paddingLeft="@dimen/buttonSmallPadding"
+            android:paddingRight="@dimen/buttonSmallPadding"
+            android:text="@string/buttonSystemSettings"
+            android:textColor="@color/buttonTextColor"
+            android:textSize="@dimen/buttonSmallTextSize"
+            />
+
+        <!-- ダミービュー -->
+        <View
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+    </LinearLayout>
+
+    <!-- 会社名 -->
+    <TextView
+        android:id="@+id/txvCompanyName"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        android:layout_marginBottom="10dp"
+        android:text="@string/companyName"
+        android:textColor="@android:color/darker_gray"
+        android:textSize="15sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <!-- バージョン -->
+    <TextView
+        android:id="@+id/txvVersion"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:layout_marginEnd="10dp"
+        android:text="@string/version"
+        android:textColor="@android:color/darker_gray"
+        android:textSize="15sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 292 - 0
app/src/main/res/layout/activity_system_settings.xml

@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activity.SystemSettingsActivity">
+
+    <!-- アクティビティヘッダ -->
+    <LinearLayout
+        android:id="@+id/lnlHeader"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/activityHeaderHeight"
+        android:background="@color/activityHeaderColor"
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <!--アクティビティヘッダ(左)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="0.5"
+            android:layout_height="match_parent">
+
+            <!-- 戻るアイコン -->
+            <Button
+                android:id="@+id/imbBack"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableStart="@drawable/ic_back_activity"
+                android:text="@string/descBack"
+                android:textSize="@dimen/buttonSmallTextSize"
+                android:textColor="@color/activityTitleTextColor"
+                android:background="@null"
+                android:layout_margin="@dimen/activityHeaderIconMargin"
+                android:layout_gravity="center_vertical"
+                />
+        </LinearLayout>
+
+        <!--アクティビティヘッダ(中央)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="match_parent">
+
+            <!--タイトル-->
+            <TextView
+                android:id="@+id/txvActivityTitle"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                android:gravity="center"
+                android:text="@string/systemSettings"
+                android:textColor="@color/activityTitleTextColor"
+                android:textSize="@dimen/activityTitleTextSize" />
+        </LinearLayout>
+
+        <!--アクティビティヘッダ(右)-->
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="0.5"
+            android:layout_height="match_parent"
+            android:gravity="end">
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- アクティビティボディ全体の垂直リニアレイアウト-->
+    <LinearLayout
+        android:id="@+id/lnlBody"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:baselineAligned="false"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toTopOf="@+id/lnlFooter"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/lnlHeader">
+
+        <!-- システム設定垂直リニアレイアウト-->
+        <LinearLayout
+            android:id="@+id/lnlSystemSettingsList"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/frameMargin"
+            android:background="@drawable/bg_frame"
+            android:baselineAligned="false"
+            android:orientation="vertical"
+            android:padding="@dimen/framePadding">
+
+            <!-- システム設定フレームタイトル-->
+            <TextView
+                android:id="@+id/txvSystemSettingsFrameTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/systemSettings"
+                android:textColor="@color/frameTitleTextColor"
+                android:textSize="@dimen/frameTitleTextSize"
+                android:textStyle="bold" />
+
+            <!-- サーバURL編集水平リニアレイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlServerUtl"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <!-- サーバURL編集タイトル-->
+                <TextView
+                    android:id="@+id/txvServerUrlTitle"
+                    android:layout_width="@dimen/systemSettingsTitleWidth"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="@dimen/itemMargin"
+                    android:text="@string/serverUrl"
+                    android:textColor="@color/titleTextColor"
+                    android:textSize="@dimen/titleTextSize" />
+
+                <!-- サーバURLエディットテキスト-->
+                <EditText
+                    android:id="@+id/edtServerUrl"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="@dimen/itemMargin"
+                    android:layout_weight="1"
+                    android:background="@drawable/bg_edittext"
+                    android:inputType="textUri"
+                    android:paddingStart="@dimen/itemMargin"
+                    android:paddingEnd="@dimen/itemMargin"
+                    android:textColor="@color/editTextColor"
+                    android:textSize="@dimen/editTextSize"
+                    android:gravity="center"
+                    />
+            </LinearLayout>
+
+            <!-- 更新時間編集水平リニアレイアウト-->
+
+            <!-- 接近判定水平リニアレイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlApproachJudgeMeter"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"/>
+
+            <!-- システム設定ボタン水平レイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlSystemSettingsButton"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="@dimen/activityFooterPadding"
+                android:orientation="horizontal">
+
+                <!-- ダミービュー -->
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:visibility="invisible" />
+
+                <!-- 保存ボタン-->
+                <Button
+                    android:id="@+id/btnSave"
+                    android:layout_width="@dimen/buttonSmallWidth"
+                    android:layout_height="@dimen/buttonSmallHeight"
+                    android:background="@drawable/bg_button"
+                    android:drawableStart="@drawable/ic_save"
+                    android:gravity="center"
+                    android:paddingLeft="@dimen/buttonSmallPadding"
+                    android:paddingRight="@dimen/buttonSmallPadding"
+                    android:text="@string/buttonSave"
+                    android:textColor="@color/buttonTextColor"
+                    android:textSize="@dimen/buttonSmallTextSize" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <!-- ユーザ認証垂直リニアレイアウト-->
+        <LinearLayout
+            android:id="@+id/lnlUserAuth"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/frameMargin"
+            android:background="@drawable/bg_frame"
+            android:baselineAligned="false"
+            android:orientation="vertical"
+            android:padding="@dimen/framePadding">
+
+            <!-- ユーザ認証フレームタイトル-->
+            <TextView
+                android:id="@+id/txvUserAuthFrameTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/userAuth"
+                android:textColor="@color/frameTitleTextColor"
+                android:textSize="@dimen/frameTitleTextSize"
+                android:textStyle="bold" />
+
+            <!-- 携帯端末ID編集水平リニアレイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlUserId"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <!-- 携帯端末ID編集タイトル-->
+                <TextView
+                    android:id="@+id/txvUserIdTitle"
+                    android:layout_width="@dimen/systemSettingsTitleWidth"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="@dimen/itemMargin"
+                    android:text="@string/userId"
+                    android:textColor="@color/titleTextColor"
+                    android:textSize="@dimen/titleTextSize" />
+
+                <!-- 携帯端末IDエディットテキスト-->
+                <EditText
+                    android:id="@+id/edtUserId"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="@dimen/itemMargin"
+                    android:layout_weight="1"
+                    android:background="@drawable/bg_edittext"
+                    android:inputType="number"
+                    android:paddingStart="@dimen/itemMargin"
+                    android:paddingEnd="@dimen/itemMargin"
+                    android:textColor="@color/editTextColor"
+                    android:textSize="@dimen/editTextSize"
+                    android:gravity="center"
+                    />
+            </LinearLayout>
+
+            <!-- MID編集水平リニアレイアウト-->
+
+            <!-- ユーザ認証ボタン水平レイアウト-->
+            <LinearLayout
+                android:id="@+id/lnlUserAuthButton"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="@dimen/activityFooterPadding"
+                android:orientation="horizontal">
+
+                <!-- ダミービュー -->
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:visibility="invisible" />
+
+                <!-- 認証ボタン-->
+                <Button
+                    android:id="@+id/btnAuth"
+                    android:layout_width="@dimen/buttonSmallWidth"
+                    android:layout_height="@dimen/buttonSmallHeight"
+                    android:background="@drawable/bg_button"
+                    android:drawableStart="@drawable/ic_ok_normal"
+                    android:gravity="center"
+                    android:paddingLeft="@dimen/buttonSmallPadding"
+                    android:paddingRight="@dimen/buttonSmallPadding"
+                    android:text="@string/buttonAuth"
+                    android:textColor="@color/buttonTextColor"
+                    android:textSize="@dimen/buttonSmallTextSize" />
+            </LinearLayout>
+
+            <!-- プログレスバー -->
+            <ProgressBar
+                android:id="@+id/prgAuth"
+                style="?android:attr/progressBarStyleHorizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:indeterminate="true"
+                android:max="100"
+                android:backgroundTint="@color/prgBarBackColor"
+                android:indeterminateTint="@color/prgBarInterminateColor"
+                android:layout_marginTop="1dp"
+                android:visibility="gone"/>
+
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- アクティビティフッタ -->
+    <LinearLayout
+        android:id="@+id/lnlFooter"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/activityFooterPadding"
+        android:paddingRight="@dimen/activityFooterPadding"
+        android:paddingBottom="@dimen/activityFooterPadding"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


BIN
app/src/main/res/raw/anzen.wav


BIN
app/src/main/res/raw/bstop.wav


BIN
app/src/main/res/raw/cose.wav


BIN
app/src/main/res/raw/error.wav


BIN
app/src/main/res/raw/opening.wav


BIN
app/src/main/res/raw/otsukare.wav


BIN
app/src/main/res/raw/tochaku.wav


+ 113 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#2872B5</color>
+    <color name="colorPrimaryDark">#154568</color>
+    <color name="colorAccent">#86C2EF</color>
+
+    <!--アクティビティヘッダの背景色-->
+    <color name="activityHeaderColor">#2F75B5</color>
+    <!--アクティビティヘッダのアイコン背景色(通常時)-->
+    <color name="activityHeaderIconNormalColor">#FFFFFF</color>
+    <!--アクティビティヘッダのアイコン背景色(押下時)-->
+    <color name="activityHeaderIconPressColor">#AAAAAA</color>
+    <!--アクティビティヘッダのアイコン背景色(無効時)-->
+    <color name="activityHeaderIconDisableColor">#EEEEEE</color>
+
+    <!--アクティビティタイトルテキスト色-->
+    <color name="activityTitleTextColor">#FFFFFF</color>
+
+    <!--アクティビティボディのアイコン背景色(通常時)-->
+    <color name="activityBodyIconNormalColor">#2F75B5</color>
+    <!--アクティビティボディのアイコン背景色(押下時)-->
+    <color name="activityBodyIconPressColor">#C2DAEF</color>
+    <!--アクティビティボディのアイコン背景色(無効時)-->
+    <color name="activityBodyIconDisableColor">#EEEEEE</color>
+
+    <!--ボタンのアイコン背景色(通常時)-->
+    <color name="buttonIconNormalColor">#2F75B5</color>
+    <!--ボタンのアイコン背景色(押下時)-->
+    <color name="buttonIconPressColor">#C2DAEF</color>
+
+    <!--ボタンの文字色-->
+    <color name="buttonTextColor">#000000</color>
+    <!--ボタンの枠線色-->
+    <color name="buttonStorokeColor">#777777</color>
+    <!--ボタンの背景色(通常時)-->
+    <color name="buttonNormalColor">#F0FFFF</color>
+    <!--ボタンの背景色(押下時)-->
+    <color name="buttonPressColor">#CCFFFF</color>
+    <!--ボタンの背景色(無効時)-->
+    <color name="buttonDisableColor">#EEEEEE</color>
+
+    <!--フレーム背景色-->
+    <color name="frameColor">#FFFFFF</color>
+    <!--フレームタイトルテキスト色-->
+    <color name="frameTitleTextColor">#000000</color>
+    <!--フレーム枠線色-->
+    <color name="frameStorokeColor">#AAAAAA</color>
+
+    <!--警告フレーム背景色-->
+    <color name="frameWarnColor">#FFB786</color>
+    <!--警告フレームタイトルテキスト色-->
+    <color name="frameWarnTitleTextColor">#000000</color>
+    <!--警告フレーム枠線色-->
+    <color name="frameWarnStorokeColor">#303030</color>
+
+    <!--エディットテキスト背景色-->
+    <color name="editColor">#FFFFC0</color>
+    <!--エディットテキスト色-->
+    <color name="editTextColor">#000000</color>
+    <!--エディットテキスト枠線色-->
+    <color name="editStorokeColor">#AAAAAA</color>
+
+    <!--タイトルテキスト色-->
+    <color name="titleTextColor">#000000</color>
+
+    <!--データテキスト背景色-->
+    <color name="dataColor">#FFFFFF</color>
+    <!--データテキスト色-->
+    <color name="dataTextColor">#000000</color>
+    <!--データ枠線色-->
+    <color name="dataStorokeColor">#AAAAAA</color>
+
+    <!--ダイアログタイトルテキスト色-->
+    <color name="dialogTitleTextColor">#FFFFFF</color>
+    <!--ダイアログタイトル背景色 -->
+    <color name="dialogTitleColor">#2F75B5</color>
+    <!--ダイアログメッセージテキスト色-->
+    <color name="dialogMsgTextColor">#000000</color>
+    <!--ダイアログメッセージ背景色 -->
+    <color name="dialogMsgColor">#FFFFFF</color>
+    <!--ダイアログボタンテキスト色-->
+    <color name="dialogBtnTextColor">#000000</color>
+
+    <!--プログレスバー背景色-->
+    <color name="prgBarBackColor">#FFFFFF</color>
+    <!--プログレスバー進捗色-->
+    <color name="prgBarInterminateColor">#22B14C</color>
+
+    <!--選択背景色-->
+    <color name="selectColor">#80FF80</color>
+    <!--未選択背景色-->
+    <color name="notSelectColor">#FFFFFF</color>
+
+
+    <!--運行情報(回送)-->
+    <color name="operationStateNeutral">#F0F0F0</color>
+    <color name="operationStateNeutralText">#000000</color>
+    <!--運行情報(準備)-->
+    <color name="operationStatePrepare">#FFFF60</color>
+    <color name="operationStatePrepareText">#000000</color>
+    <!--運行情報(運行中)-->
+    <color name="operationStateActive">#00A6FF</color>
+    <color name="operationStateActiveText">#000000</color>
+
+    <!--通過ポイントの接近状態(初期状態)-->
+    <color name="approachColorInit">#FFFFFF</color>
+    <!--通過ポイントの接近状態(接近)-->
+    <color name="approachColorApproach">#BDFFFF</color>
+    <!--通過ポイントの接近状態(到着)-->
+    <color name="approachColorArrial">#80FF80</color>
+    <!--通過ポイントの接近状態(通過)-->
+    <color name="approachColorPassed">#FFFF80</color>
+</resources>

+ 114 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--アクティビティヘッダの高さ-->
+    <dimen name="activityHeaderHeight">48dp</dimen>
+    <!--アクティビティヘッダ内のアイコンサイズ-->
+    <dimen name="activityHeaderIconSize">24dp</dimen>
+    <!--アクティビティヘッダ内のアイコンマージン-->
+    <dimen name="activityHeaderIconMargin">10dp</dimen>
+    <!--アクティビティタイトルテキストサイズ-->
+    <dimen name="activityTitleTextSize">24sp</dimen>
+    <!--アクティビティフッタの高さ-->
+    <dimen name="activityFooterHeight">48dp</dimen>
+    <!--アクティビティフッタのパディング-->
+    <dimen name="activityFooterPadding">10dp</dimen>
+
+    <!--中ボタンの幅-->
+    <dimen name="buttonMiddleWidth">260dp</dimen>
+    <!--中ボタンの高さ-->
+    <dimen name="buttonMiddleHeight">56dp</dimen>
+    <!--中ボタンのテキストサイズ-->
+    <dimen name="buttonMiddleTextSize">24sp</dimen>
+    <!--中ボタン内のアイコンサイズ-->
+    <dimen name="buttonMiddleIconSize">24dp</dimen>
+    <!--中ボタン内のパディングサイズ-->
+    <dimen name="buttonMiddlePadding">10dp</dimen>
+    <!--中ボタンの枠線幅-->
+    <dimen name="buttonMiddleStrokeWidth">1dp</dimen>
+    <!--中ボタンの丸み-->
+    <dimen name="buttonMiddleRadius">10dp</dimen>
+
+    <!--小ボタンの幅-->
+    <dimen name="buttonSmallWidth">140dp</dimen>
+    <!--小ボタンの高さ-->
+    <dimen name="buttonSmallHeight">48dp</dimen>
+    <!--小ボタンのテキストサイズ-->
+    <dimen name="buttonSmallTextSize">18sp</dimen>
+    <!--小ボタン内のアイコンサイズ-->
+    <dimen name="buttonSmallIconSize">24dp</dimen>
+    <!--小ボタン内のパディングサイズ-->
+    <dimen name="buttonSmallPadding">10dp</dimen>
+    <!--小ボタンの枠線幅-->
+    <dimen name="buttonSmallStrokeWidth">1dp</dimen>
+    <!--小ボタンの丸み-->
+    <dimen name="buttonSmallRadius">5dp</dimen>
+
+    <!--フレームタイトルテキストサイズ-->
+    <dimen name="frameTitleTextSize">18sp</dimen>
+    <!--フレームタイトル幅-->
+    <dimen name="frameTitleWidth">150dp</dimen>
+    <!--フレームマージン-->
+    <dimen name="frameMargin">10dp</dimen>
+    <!--フレームパディング-->
+    <dimen name="framePadding">10dp</dimen>
+    <!--フレームの枠線幅-->
+    <dimen name="frameStrokeWidth">1dp</dimen>
+
+    <!--項目のマージン-->
+    <dimen name="itemMargin">10dp</dimen>
+
+    <!--エディットテキストの枠線幅-->
+    <dimen name="editStrokeWidth">1dp</dimen>
+    <!--エディットテキストのテキストサイズ-->
+    <dimen name="editTextSize">18sp</dimen>
+
+    <!--タイトルテキストサイズ-->
+    <dimen name="titleTextSize">18sp</dimen>
+
+    <!--データの枠線幅-->
+    <dimen name="dataStrokeWidth">1dp</dimen>
+    <!--データテキストサイズ-->
+    <dimen name="dataTextSize">18sp</dimen>
+    <!--データテキストサイズ(小)-->
+    <dimen name="dataTextSizeSmall">14sp</dimen>
+
+    <!--ダイアログタイトルテキストサイズ-->
+    <dimen name="dialogTitleTextSize">18sp</dimen>
+    <!--ダイアログタイトルパディング-->
+    <dimen name="dialogTitlePadding">20dp</dimen>
+    <!--ダイアログメッセージテキストサイズ-->
+    <dimen name="dialogMsgTextSize">18sp</dimen>
+    <!--ダイアログメッセージパディング-->
+    <dimen name="dialogMsgPadding">20dp</dimen>
+    <!--ダイアログボタンテキストサイズ-->
+    <dimen name="dialogBtnTextSize">18sp</dimen>
+    <!--ダイアログ入力テキストパディング-->
+    <dimen name="dialogEditPadding">10dp</dimen>
+    <!--ダイアログボタンの幅-->
+    <dimen name="dialogBtnWidth">140dp</dimen>
+    <!--ダイアログボタンの高さ-->
+    <dimen name="dialogBtnHeight">48dp</dimen>
+    <!--ダイアログボタンのマージン-->
+    <dimen name="dialogBtnMargin">20dp</dimen>
+
+    <!--リストビュー項目のマージン-->
+    <dimen name="listViewitemMargin">10dp</dimen>
+    <!--リストビュー項目のテキストマージン-->
+    <dimen name="listViewitemTextMargin">1dp</dimen>
+    <!--リストビュー項目のテキストパディング-->
+    <dimen name="listViewitemTextPadding">10dp</dimen>
+    <!--リストビューボタンの幅-->
+    <dimen name="listViewButtonWidth">28dp</dimen>
+    <!--リストビューボタンの高さ-->
+    <dimen name="listViewButtonHeight">28dp</dimen>
+    <!--小ボタン内のアイコンサイズ-->
+    <dimen name="listViewIconSize">18dp</dimen>
+
+    <!--システム設定のタイトル幅-->
+    <dimen name="systemSettingsTitleWidth">150dp</dimen>
+
+    <!--運行状況表示部テキストサイズ-->
+    <dimen name="nowDriveStatusTextSize">30sp</dimen>
+
+
+</resources>

+ 80 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,80 @@
+<resources>
+    <string name="app_name">運行管理システム</string>
+    <string name="version">Version.</string>
+    <string name="companyName">ECOSYSNETWORK K.K.</string>
+    <string name="buttonStart">"開始 "</string>
+    <string name="buttonCourseSettings">"コース設定 "</string>
+    <string name="buttonSystemSettings">"システム設定 "</string>
+    <string name="descBack">戻る</string>
+    <string name="courseSettings">"コース設定"</string>
+    <string name="courseSelect">"コース選択"</string>
+    <string name="courseSelected">"選択中コース"</string>
+    <string name="nowDriveStatus">"現在の運行状況"</string>
+    <string name="courseSelectRequest">"コースを選択してください"</string>
+    <string name="buttonStartDrive">"運行を開始"</string>
+    <string name="buttonEndDrive">"運行を終了"</string>
+    <string name="courseChange">"コース変更"</string>
+    <string name="systemSettings">"システム設定"</string>
+    <string name="buttonSave">"保存 "</string>
+    <string name="buttonAuth">"認証 "</string>
+    <string name="buttonUpdateCourse">"コース更新 "</string>
+    <string name="buttonChangeCourse">"コース変更 "</string>
+    <string name="buttonExit">"終了 "</string>
+    <string name="buttonOk">"OK "</string>
+    <string name="buttonCancel">"キャンセル "</string>
+    <string name="serverUrl">"URL"</string>
+    <string name="updateCycleMsec">"更新時間(msec)"</string>
+    <string name="approachJudgeMeter">"接近判定(m)"</string>
+    <string name="userAuth">"携帯端末認証"</string>
+    <string name="userId">"携帯端末ID"</string>
+    <string name="inpPassword">"パスワードを入力し、OKボタンを押して下さい。"</string>
+    <string name="ok">OK</string>
+    <string name="cancel">キャンセル</string>
+    <string name="titleCfm">確認</string>
+    <string name="titleError">エラー</string>
+    <string name="yes">はい</string>
+    <string name="no">いいえ</string>
+    <string name="driveNotifyName">運行管理システム</string>
+    <string name="driveNotifyDescription">お知らせを通知します。</string>
+    <string name="driveNotifyName2">運行管理システム</string>
+    <string name="driveNotifyDescription2">運転中です。</string>
+    <string name="msgNeqCourseSettingPassword">パスワードが違います。</string>
+    <string name="msgNeqSystemSettingPassword">パスワードが違います。</string>
+    <string name="msgSystemSettingSave">システム設定データを保存します。\nよろしいですか?</string>
+    <string name="msgCourseSettingSave">コース設定データを保存します。\nよろしいですか?</string>
+    <string name="msgUserAuth">携帯端末認証を行います。\nよろしいですか?</string>
+    <string name="msgUpdateCourse">コースの更新を行います。\nよろしいですか?</string>
+    <string name="msgCommunicationError">通信エラーが発生しました。</string>
+    <string name="msgCommunicateCmpltError">通信終了処理でエラーが発生しました。</string>
+    <string name="msgSuccessUserAuth">携帯端末認証に成功しました。</string>
+    <string name="msgSuccessUserAuthButSaveError">携帯端末認証に成功しましたが、端末への保存に失敗しました。</string>
+    <string name="msgConfStartCourse">運行を開始しますか?</string>
+    <string name="msgSuccessStartCourse">運行が正常に開始されました</string>
+    <string name="msgAppExist">運行を終了しますと、位置情報が送信されなくなります。\nよろしいですか?</string>
+    <string name="msgSetPhoneId">管理者に連絡し携帯端末IDを設定してください。</string>
+    <string name="msgSetCourse">運行コースを設定してください。</string>
+    <string name="msgResetCourse">日付が変わりました。コースを再度設定しなおしてください</string>
+    <string name="commErrmsg01">"端末ID不明"</string>
+    <string name="commErrmsg02">"位置情報不明"</string>
+    <string name="commErrmsg03">"位置情報不明"</string>
+    <string name="commErrmsg04">"携帯端末ーID不明"</string>
+    <string name="commErrmsg05">"画像オーバー"</string>
+    <string name="commErrmsg06">"ファイル形式不明"</string>
+    <string name="commErrmsg07">"UPLOAD_ERR_INI_SIZE"</string>
+    <string name="commErrmsg08">"UPLOAD_ERR_FORM_SIZE"</string>
+    <string name="commErrmsg09">"UPLOAD_ERR_PARTIAL"</string>
+    <string name="commErrmsg10">"UPLOAD_ERR_NO_FILE"</string>
+    <string name="commErrmsg11">"UPLOAD_ERR_NO_TMP_DIR"</string>
+    <string name="commErrmsg12">"UPLOAD_ERR_CANT_WRITE"</string>
+    <string name="commErrmsg13">"UPLOAD_ERR_EXTENSION"</string>
+    <string name="commErrmsg14">"UPLOAD_ERR_UNKNOWN"</string>
+    <string name="commErrmsg15">"サイズオーバー"</string>
+    <string name="commErrmsgOT">"その他/通信異常"</string>
+    <string name="descIconListView">アイコン</string>
+    <string name="msgSuccessUpdateCourse">コース更新に成功しました。</string>
+    <string name="msgSuccessUpdateCourseButSaveError">コース更新に成功しましたが、端末への保存に失敗しました。</string>
+    <string name="descCommIcon">緑はサーバとの通信が正常。赤はエラーを表します。</string>
+    <string name="descLocationIcon">緑は位置情報の取得が正常。赤はエラーを表します。</string>
+    <string name="driveStateStart">運行中</string>
+    <string name="driveStateEnd">運行外</string>
+</resources>

+ 10 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,10 @@
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>

+ 24 - 0
build.gradle

@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.0.2'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 19 - 0
gradle.properties

@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Thu Oct 15 14:33:50 JST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 2 - 0
settings.gradle

@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "ccloca_app"