Conflicts: app/src/main/AndroidManifest.xml app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.javamaster
| @@ -0,0 +1,54 @@ | |||||
| ## NSGK App | |||||
| > 首页地址列表 | |||||
| | 名称 | 地址(实际需自行添加协议,地址,端口) | 图标 | 圆角图标 | | |||||
| |:-----|:-----|:-----|:-----| | |||||
| | 事项审批 | /yinnongLogin | ic_launcher_sxsp 或者 ic_launcher_yhzl | ic_launcher_sxsp_round 或者 ic_launcher_yhzl_round | | |||||
| | 阳光村务(村级事项) | /sunVillage_info/login | ic_launcher_ygcw | ic_launcher_ygcw_round | | |||||
| | 大托管 | /agriculturalTrusteeship/index | ic_launcher_dtg | ic_launcher_dtg_round | | |||||
| | 产权交易 | /index | ic_launcher_cqjy | ic_launcher_cqjy_round | | |||||
| | 农业执法 | /lawEnforcement/login | ic_launcher_nyzf | ic_launcher_nyzf_round | | |||||
| | 宅基地审批(乌市版) | /zjdLogin | ic_launcher_zjd | ic_launcher_zjd_round | | |||||
| | 宅基地调查 | /homesteadLogin | ic_launcher_zjddc | ic_launcher_zjddc_round | | |||||
| | 两清三化 | /homestead/login | ic_launcher_2q3h | ic_launcher_2q3h_round | | |||||
| | 网上家园(废) | /onlineHomeLogin | ic_launcher_white | ic_launcher_white_round | | |||||
| | 确权调查 | /contracted/login | ic_launcher_qqdc | ic_launcher_qqdc_round | | |||||
| | 一体机 | / | ic_launcher_njytj | ic_launcher_njytj_round | | |||||
| > 使用`Android Studio`修改步骤 | |||||
| * 编辑`gradle.properties`文件 | |||||
| * 配置```appHomeUrl=```首页地址, 不要加双引号 | |||||
| * 配置```appName=```App桌面快捷方式名称, 可以直接使用字符串(需要加双引号, 如 "NSGK APP"), 也可以使用国际化字符串配置(不要加双引号, 如 @string/app_name) | |||||
| * 配置```appIconKey=```图标名称, 不要加双引号, 去掉前后缀(如 yhzl, 图标为ic_launcher_yhzl; 圆角图标为ic_launcher_yhzl_round) | |||||
| * 使用Android Studio生成签名包 | |||||
| * 配置```appCopyright=```启动页版权文本, 不要加双引号 | |||||
| * 配置```appVendor=```启动页厂商名称, 不要加双引号 | |||||
| * 配置```appUpdateUrl=```apk下载更新地址, 不要加双引号 | |||||
| * 生成的apk路径为 `/app/release/app-release.apk` | |||||
| > 使用`gradle`脚本打签名的正式包 | |||||
| * 执行 ```gradlew assembleRelease``` 将使用`gradle.properties`文件里的配置进行打包 | |||||
| * 如果需要自定义配置(无需修改`gradle.properties`文件), 执行 ```gradlew assembleRelease -PappHomeUrl="首页地址" -PappName="App桌面快捷方式名称(只能使用字符串)" -PappIconKey="图标名称" -PappIconKey="启动页版权文本" -PappCopyright="启动页厂商名称" -PappUpdateUrl="apk下载更新地址"``` 将使用命令行里的配置进行打包 | |||||
| * 生成的apk路径为 `/app/build/outputs/apk/release/app-release.apk` | |||||
| > 帮助脚本 | |||||
| * `打包-正式.bat`: 构建正式包(直接执行将使用`gradle.properties`文件里的配置进行打包, 完整命令行用法为 ```.\打包-正式.bat App主页链接地址 App名称 App图标简称 App启动页版权文本 App启动页厂商名称```, 此时会使用命令行里的配置进行打包. 生成的路径为`/app/build/outputs/apk/release/app-release.apk`) | |||||
| * `打包-debug.bat`: 构建Debug包(生成的路径为`/app/build/outputs/apk/debug/app-debug.apk`) | |||||
| * `安装-发布包.bat`: 将已打好的Release包安装至手机(手机需连接到电脑, 并且启用开发者模式) | |||||
| * `安装-调试包.bat`: 将已打好的Debug包安装至手机(手机需连接到电脑, 并且启用开发者模式) | |||||
| > 签名证书(安装签名的发布包需要先卸载已安装的debug包) | |||||
| * 路径: `/app-keystore.jks` | |||||
| * 密码: `ns61GK32x%` | |||||
| * Key Alias: `nsgk_rural_web` | |||||
| * Key Password: `ns61GK32x%` | |||||
| > 示例 | |||||
| ```shell | |||||
| .\打包-正式.bat http://mxixiaxian.nongshen.net/sunVillage_info/login_code_new 阳光三资 ygcw | |||||
| ``` | |||||
| @@ -10,15 +10,66 @@ android { | |||||
| minSdkVersion 24 | minSdkVersion 24 | ||||
| targetSdkVersion 30 | targetSdkVersion 30 | ||||
| versionCode 1 | versionCode 1 | ||||
| versionName "1.0" | |||||
| versionName "1.0.0.1" | |||||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
| // App首页链接地址 | |||||
| buildConfigField "String", "APP_HOME_URL", "\"${project.properties.appHomeUrl}\"" | |||||
| // App启动页版权文本 | |||||
| buildConfigField "String", "APP_COMPYRIGHT", "\"${project.properties.appCopyright}\"" | |||||
| // App启动页厂商名称 | |||||
| buildConfigField "String", "APP_VENDOR", "\"${project.properties.appVendor}\"" | |||||
| // App图标 | |||||
| buildConfigField "String", "APP_ICON", "\"${project.properties.appIconKey}\"" | |||||
| // App更新下载地址 | |||||
| buildConfigField "String", "APP_UPDATE_URL", "\"${project.properties.appUpdateUrl}\"" | |||||
| // AndroidManifest.xml占位符 | |||||
| manifestPlaceholders = [ | |||||
| // App名称 | |||||
| APP_NAME: "${project.properties.appName}", | |||||
| // App图标 | |||||
| APP_ICON: "@mipmap/ic_launcher_${project.properties.appIconKey}", | |||||
| // App圆角图标 | |||||
| APP_ROUND_ICON: "@mipmap/ic_launcher_${project.properties.appIconKey}_round", | |||||
| // 高德key | |||||
| AMAP_KEY: "${project.properties.amapKey}", | |||||
| ] | |||||
| } | |||||
| signingConfigs { | |||||
| release { | |||||
| v1SigningEnabled true | |||||
| v2SigningEnabled true | |||||
| storeFile file(project.properties.storeFile) | |||||
| storePassword project.properties.storePassword | |||||
| keyAlias project.properties.keyAlias | |||||
| keyPassword project.properties.keyPassword | |||||
| } | |||||
| } | } | ||||
| buildTypes { | buildTypes { | ||||
| release { | release { | ||||
| minifyEnabled false | minifyEnabled false | ||||
| proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||
| signingConfig signingConfigs.release | |||||
| resValue("string", "app_home_url", "\"${project.properties.appHomeUrl}\"") | |||||
| resValue("string", "app_copyright", "\"${project.properties.appCopyright}\"") | |||||
| resValue("string", "app_vendor", "\"${project.properties.appVendor}\"") | |||||
| resValue("string", "app_name2", "\"${project.properties.appName}\"") | |||||
| resValue("string", "app_update_url", "\"${project.properties.appUpdateUrl}\"") | |||||
| } | |||||
| debug { | |||||
| signingConfig signingConfigs.release | |||||
| resValue("string", "app_home_url", "\"${project.properties.appHomeUrl}\"") | |||||
| resValue("string", "app_copyright", "\"${project.properties.appCopyright}\"") | |||||
| resValue("string", "app_vendor", "\"${project.properties.appVendor}\"") | |||||
| resValue("string", "app_name2", "\"${project.properties.appName}\"") | |||||
| resValue("string", "app_update_url", "\"${project.properties.appUpdateUrl}\"") | |||||
| } | } | ||||
| } | } | ||||
| compileOptions { | compileOptions { | ||||
| @@ -39,4 +90,9 @@ dependencies { | |||||
| androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' | ||||
| implementation 'com.github.Justson.AgentWeb:agentweb-core:v4.1.9-androidx' // (必选) | implementation 'com.github.Justson.AgentWeb:agentweb-core:v4.1.9-androidx' // (必选) | ||||
| implementation 'com.github.Justson.AgentWeb:agentweb-filechooser:v4.1.9-androidx' | implementation 'com.github.Justson.AgentWeb:agentweb-filechooser:v4.1.9-androidx' | ||||
| } | |||||
| implementation 'androidx.preference:preference:1.1.1' | |||||
| implementation 'cn.hutool:hutool-all:5.5.4' | |||||
| compile fileTree(include: ['*.jar'], dir: 'libs') | |||||
| //compile 'com.amap.api:location:latest.integration' | |||||
| } | |||||
| @@ -13,33 +13,63 @@ | |||||
| <uses-permission android:name="android.permission.CAMERA" /> | <uses-permission android:name="android.permission.CAMERA" /> | ||||
| <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> | <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> | ||||
| <!--AgentWeb 是默认允许定位的 ,如果你需要该功能 , 请在你的 AndroidManifest 文件里面加入如下权限 。--> | <!--AgentWeb 是默认允许定位的 ,如果你需要该功能 , 请在你的 AndroidManifest 文件里面加入如下权限 。--> | ||||
| <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> | |||||
| <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> | |||||
| <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||
| <!--用于获取wifi的获取权限,wifi信息会用来进行网络定位--> | |||||
| <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission> | |||||
| <!--用于申请调用A-GPS模块--> | |||||
| <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission> | |||||
| <!--如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限--> | |||||
| <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> | |||||
| <!--如果您的应用需要后台定位权限,且有可能运行在Android Q设备上,并且设置了target>28,必须增加这个权限声明--> | |||||
| <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> | |||||
| <!-- android:icon 、 android:roundIcon 是APP显示图标--> | <!-- android:icon 、 android:roundIcon 是APP显示图标--> | ||||
| <!-- | |||||
| APP_NAME占位符: | |||||
| 可以在 /gradle.properties 里配置 appName="自定义名称", 必须携带双引号; 或appName=@string/app_name, 使用国际化文件字符串资源时不要携带双引号 | |||||
| 也可以在命令行添加 -PappName="自定义名称", 必须携带双引号; 不支持国际化文件字符串资源 | |||||
| APP_ICON占位符: | |||||
| APP_ROUND_ICON占位符: | |||||
| 可以在 /gradle.properties 里配置 appIconKey=图标类型, 不要携带双引号 | |||||
| 也可以在命令行添加 -PappIconKey=图标类型, 不要携带双引号 | |||||
| APP_ICON拼接为 @mipmap/ic_launcher_${APP_ICON} | |||||
| APP_ROUND_ICON拼接为 @mipmap/ic_launcher_${APP_ICON}_round | |||||
| --> | |||||
| <application | <application | ||||
| android:requestLegacyExternalStorage="true" | android:requestLegacyExternalStorage="true" | ||||
| android:allowBackup="true" | android:allowBackup="true" | ||||
| android:icon="@mipmap/ic_launcher_zjddc" | |||||
| android:roundIcon="@mipmap/ic_launcher_zjddc_round" | |||||
| android:label="@string/app_name" | |||||
| android:icon="${APP_ICON}" | |||||
| android:roundIcon="${APP_ROUND_ICON}" | |||||
| android:label="${APP_NAME}" | |||||
| android:supportsRtl="true" | android:supportsRtl="true" | ||||
| android:usesCleartextTraffic="true" | android:usesCleartextTraffic="true" | ||||
| android:theme="@style/Theme.Nsgk_rural_web"> | android:theme="@style/Theme.Nsgk_rural_web"> | ||||
| <meta-data | |||||
| android:name="com.amap.api.v2.apikey" | |||||
| android:value="${AMAP_KEY}"/> | |||||
| <activity | <activity | ||||
| android:name=".WelcomeActivity" | android:name=".WelcomeActivity" | ||||
| android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" | android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" | ||||
| android:label="@string/app_name" | |||||
| android:label="${APP_NAME}" | |||||
| android:theme="@style/Theme.Nsgk_rural_web.Fullscreen"> | android:theme="@style/Theme.Nsgk_rural_web.Fullscreen"> | ||||
| <intent-filter> | <intent-filter> | ||||
| <action android:name="android.intent.action.MAIN" /> | <action android:name="android.intent.action.MAIN" /> | ||||
| <category android:name="android.intent.category.LAUNCHER" /> | <category android:name="android.intent.category.LAUNCHER" /> | ||||
| </intent-filter> | </intent-filter> | ||||
| <meta-data android:name="android.app.shortcuts" | |||||
| android:resource="@xml/shortcuts" /> | |||||
| </activity> | </activity> | ||||
| <activity | <activity | ||||
| android:name=".FullscreenActivity" | android:name=".FullscreenActivity" | ||||
| android:label="${APP_NAME}" | |||||
| android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" /> | |||||
| <activity | |||||
| android:name=".SettingsActivity" | |||||
| android:label="设置" | |||||
| android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" /> | android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" /> | ||||
| <!--开机广播接受者 | <!--开机广播接受者 | ||||
| @@ -49,6 +79,21 @@ | |||||
| </intent-filter> | </intent-filter> | ||||
| </receiver> | </receiver> | ||||
| --> | --> | ||||
| <service android:name="com.amap.api.location.APSService"></service> | |||||
| <provider | |||||
| android:name="androidx.core.content.FileProvider" | |||||
| android:authorities="${applicationId}.fileprovider" | |||||
| android:exported="false" | |||||
| android:grantUriPermissions="true" | |||||
| android:permission="android.permission.MANAGE_DOCUMENTS"> | |||||
| <!-- <intent-filter> | |||||
| <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> | |||||
| </intent-filter>--> | |||||
| <meta-data | |||||
| android:name="android.support.FILE_PROVIDER_PATHS" | |||||
| android:resource="@xml/file_paths" /> | |||||
| </provider> | |||||
| </application> | </application> | ||||
| </manifest> | </manifest> | ||||
| @@ -1,28 +1,62 @@ | |||||
| package com.nsgk.ruralWeb; | package com.nsgk.ruralWeb; | ||||
| import android.annotation.SuppressLint; | import android.annotation.SuppressLint; | ||||
| import android.content.Context; | |||||
| import android.content.Intent; | import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | |||||
| import android.graphics.Bitmap; | |||||
| import android.os.Build; | |||||
| import android.os.Bundle; | import android.os.Bundle; | ||||
| import android.os.Handler; | |||||
| import android.util.Log; | |||||
| import android.view.KeyEvent; | import android.view.KeyEvent; | ||||
| import android.view.View; | |||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||
| import android.webkit.ConsoleMessage; | |||||
| import android.webkit.GeolocationPermissions; | |||||
| import android.webkit.WebBackForwardList; | |||||
| import android.webkit.WebHistoryItem; | |||||
| import android.webkit.WebSettings; | |||||
| import android.webkit.WebView; | |||||
| import android.widget.LinearLayout; | import android.widget.LinearLayout; | ||||
| import android.widget.ProgressBar; | |||||
| import android.widget.RelativeLayout; | import android.widget.RelativeLayout; | ||||
| import android.widget.Toast; | |||||
| import com.just.agentweb.AgentWeb; | import com.just.agentweb.AgentWeb; | ||||
| import com.just.agentweb.AgentWebConfig; | |||||
| import com.just.agentweb.DefaultWebClient; | import com.just.agentweb.DefaultWebClient; | ||||
| import com.just.agentweb.WebChromeClient; | |||||
| import com.just.agentweb.WebViewClient; | |||||
| import com.nsgk.ruralWeb.sys.NSConstants; | |||||
| import com.nsgk.ruralWeb.sys.NSPreference; | |||||
| import com.nsgk.ruralWeb.utils.NSContextUtils; | |||||
| import com.nsgk.ruralWeb.utils.NSStr; | |||||
| import com.nsgk.ruralWeb.web.NSEnvWindowObject; | |||||
| import androidx.annotation.NonNull; | |||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||
| import cn.hutool.core.util.StrUtil; | |||||
| /** | /** | ||||
| * An example full-screen activity that shows and hides the system UI (i.e. | * An example full-screen activity that shows and hides the system UI (i.e. | ||||
| * status bar and navigation/system bar) with user interaction. | * status bar and navigation/system bar) with user interaction. | ||||
| */ | */ | ||||
| public class FullscreenActivity extends AppCompatActivity { | public class FullscreenActivity extends AppCompatActivity { | ||||
| private static final String ID_TAG = FullscreenActivity.class.getName(); | |||||
| public static final int PERMISSION_LOCATION_REQUEST_CODE = 0x1001; | |||||
| private AgentWeb mAgentWeb; | private AgentWeb mAgentWeb; | ||||
| private NSPreference preference; | |||||
| private NSEnvWindowObject envWindowObject; | |||||
| private String m_lastUrl; | |||||
| private String m_mainUrl; | |||||
| private View m_progressIndicator; | |||||
| @SuppressLint("SetJavaScriptEnabled") | @SuppressLint("SetJavaScriptEnabled") | ||||
| @Override | @Override | ||||
| @@ -30,12 +64,71 @@ public class FullscreenActivity extends AppCompatActivity { | |||||
| super.onCreate(savedInstanceState); | super.onCreate(savedInstanceState); | ||||
| setContentView(R.layout.activity_fullscreen); | setContentView(R.layout.activity_fullscreen); | ||||
| String appHomeUrl = GetHomeUrl(); | |||||
| boolean logcatConsole = GetPreference().GetBool(NSPreference.LOGCAT_CONSOLE_OUTPUT, NSContextUtils.BuildIsDebug(this)); | |||||
| if(logcatConsole) | |||||
| Log.i(ID_TAG, logcatConsole ? "输出控制台到logcat" : "不输出控制台"); | |||||
| //appHomeUrl = "http://192.168.0.250:85/sunVillage_info/login"; | |||||
| Log.i(ID_TAG, "App home url: " + appHomeUrl); | |||||
| // init(); | // init(); | ||||
| mAgentWeb = AgentWeb.with(this)// | |||||
| .setAgentWebParent((RelativeLayout) findViewById(R.id.ll), -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))//传入AgentWeb的父控件。 | |||||
| AgentWeb.PreAgentWeb builder = AgentWeb.with(this)// | |||||
| .setAgentWebParent(findViewById(R.id.ll), -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))//传入AgentWeb的父控件。 | |||||
| .useDefaultIndicator(-1, 3)//设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 | .useDefaultIndicator(-1, 3)//设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 | ||||
| .setWebViewClient(new com.just.agentweb.WebViewClient() { | |||||
| .setWebChromeClient(new WebChromeClient() { | |||||
| @Override | |||||
| public void onProgressChanged(WebView view, int newProgress) | |||||
| { | |||||
| super.onProgressChanged(view, newProgress); | |||||
| if(newProgress != 100) | |||||
| return; | |||||
| String url = view.getUrl(); | |||||
| if(IsInDefaultPage()) | |||||
| { | |||||
| m_mainUrl = url; | |||||
| DumpMainUrl(url); | |||||
| Log.i(ID_TAG, "设定主页地址: " + m_mainUrl); | |||||
| } | |||||
| else if(IsDefaultPage(url)) | |||||
| { | |||||
| m_mainUrl = null; | |||||
| Log.i(ID_TAG, "清空主页地址"); | |||||
| } | |||||
| m_lastUrl = url; | |||||
| } | |||||
| @Override | |||||
| public boolean onConsoleMessage(ConsoleMessage consoleMessage) | |||||
| { | |||||
| if(logcatConsole) | |||||
| { | |||||
| String text = "[" + consoleMessage.sourceId() + ":" + consoleMessage.lineNumber() + "] " + consoleMessage.message(); | |||||
| switch(consoleMessage.messageLevel()) | |||||
| { | |||||
| case WARNING: | |||||
| Log.w("Console", text); | |||||
| break; | |||||
| case ERROR: | |||||
| Log.e("Console", text); | |||||
| break; | |||||
| case DEBUG: | |||||
| Log.d("Console", text); | |||||
| break; | |||||
| case LOG: | |||||
| default: | |||||
| Log.i("Console", text); | |||||
| break; | |||||
| } | |||||
| } | |||||
| return super.onConsoleMessage(consoleMessage); | |||||
| } | |||||
| @Override | |||||
| public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { | |||||
| callback.invoke(origin, true, false); | |||||
| super.onGeolocationPermissionsShowPrompt(origin, callback); | |||||
| } | |||||
| }) | |||||
| .setWebViewClient(new WebViewClient() { | |||||
| })//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 | })//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 | ||||
| .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 | .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 | ||||
| .setMainFrameErrorView(R.layout.agentweb_error_page, -1) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。 | .setMainFrameErrorView(R.layout.agentweb_error_page, -1) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。 | ||||
| @@ -43,7 +136,51 @@ public class FullscreenActivity extends AppCompatActivity { | |||||
| .interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 | .interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 | ||||
| .createAgentWeb()//创建AgentWeb。 | .createAgentWeb()//创建AgentWeb。 | ||||
| .ready()//设置 WebSettings。 | .ready()//设置 WebSettings。 | ||||
| .go("http://218.59.175.43:71/homesteadLogin"); //WebView载入该url地址的页面并显示。 | |||||
| ; | |||||
| boolean openDef = IsDefaultPage(appHomeUrl); | |||||
| if(!openDef) | |||||
| { | |||||
| Log.i(ID_TAG, "载入主页: " + appHomeUrl); | |||||
| RestoreCookie(); // 重载cookie | |||||
| m_mainUrl = appHomeUrl; | |||||
| } | |||||
| else | |||||
| { | |||||
| Log.i(ID_TAG, "载入登录页"); | |||||
| GetPreference().Remove(NSPreference.COOKIES); // Remove cookies | |||||
| } | |||||
| mAgentWeb = builder.go(appHomeUrl); //WebView载入该url地址的页面并显示。 | |||||
| WebSettings settings = GetWebView().getSettings(); | |||||
| int fontScale = GetPreference().GetInt(NSPreference.FONT_SCALE); | |||||
| Log.i(ID_TAG, "全局字体缩放: " + fontScale + "%"); | |||||
| if(fontScale > 0 && fontScale != 100) | |||||
| settings.setTextZoom(fontScale); | |||||
| settings.setDatabaseEnabled(true); | |||||
| settings.setDomStorageEnabled(true); | |||||
| settings.setGeolocationEnabled(true); | |||||
| // 注入宿主对象 | |||||
| envWindowObject = new NSEnvWindowObject(this, new Handler(), GetWebView()); | |||||
| mAgentWeb.getJsInterfaceHolder().addJavaObject("_Native_object", envWindowObject); | |||||
| mAgentWeb.getJsInterfaceHolder().addJavaObject("Android", envWindowObject); | |||||
| // 等待条指示器 | |||||
| m_progressIndicator = getLayoutInflater().inflate(R.layout.location_indicator, null); | |||||
| m_progressIndicator.findViewById(R.id.terminate_button).setOnClickListener((View view) -> { | |||||
| envWindowObject.TerminateLocation(); | |||||
| }); | |||||
| m_progressIndicator.setVisibility(View.GONE); | |||||
| m_progressIndicator.setZ(99); | |||||
| RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); | |||||
| params.addRule(RelativeLayout.CENTER_IN_PARENT); | |||||
| ((RelativeLayout)findViewById(R.id.ll)).addView(m_progressIndicator, params); | |||||
| /* 上边url是各个APP项目的入口地址 | /* 上边url是各个APP项目的入口地址 | ||||
| 事项审批 http://116.255.223.226:82/yinnongLogin 图标 ic_launcher_sxsp 或者 ic_launcher_yhzl | 事项审批 http://116.255.223.226:82/yinnongLogin 图标 ic_launcher_sxsp 或者 ic_launcher_yhzl | ||||
| @@ -59,10 +196,15 @@ public class FullscreenActivity extends AppCompatActivity { | |||||
| 一体机 http://47.98.113.57:81 图标 ic_launcher_njytj | 一体机 http://47.98.113.57:81 图标 ic_launcher_njytj | ||||
| */ | */ | ||||
| Log.d(ID_TAG, "UI线程: " + Thread.currentThread().getId()); | |||||
| } | } | ||||
| @Override | @Override | ||||
| protected void onPause() { | protected void onPause() { | ||||
| if(IsRecordUrl()) | |||||
| { | |||||
| DumpCookie(); | |||||
| } | |||||
| mAgentWeb.getWebLifeCycle().onPause(); | mAgentWeb.getWebLifeCycle().onPause(); | ||||
| super.onPause(); | super.onPause(); | ||||
| } | } | ||||
| @@ -75,25 +217,165 @@ public class FullscreenActivity extends AppCompatActivity { | |||||
| @Override | @Override | ||||
| protected void onDestroy() { | protected void onDestroy() { | ||||
| if(!IsRecordUrl()) | |||||
| { | |||||
| Log.d(ID_TAG, "Remove url and cookie record"); | |||||
| GetPreference().Remove(NSPreference.COOKIES); | |||||
| GetPreference().Remove(NSPreference.LAST_ACCESS_URL); | |||||
| } | |||||
| mAgentWeb.getWebLifeCycle().onDestroy(); | mAgentWeb.getWebLifeCycle().onDestroy(); | ||||
| envWindowObject.OnDestroy(); | |||||
| super.onDestroy(); | super.onDestroy(); | ||||
| } | } | ||||
| @Override | @Override | ||||
| public boolean onKeyDown(int keyCode, KeyEvent event) { | public boolean onKeyDown(int keyCode, KeyEvent event) { | ||||
| // if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) { | |||||
| // mWebView.goBack(); | |||||
| // return true; | |||||
| // } | |||||
| if (keyCode == KeyEvent.KEYCODE_BACK && HandleBackKeyEvent(1)) { | |||||
| return true; | |||||
| } | |||||
| if (mAgentWeb.handleKeyEvent(keyCode, event)) { | if (mAgentWeb.handleKeyEvent(keyCode, event)) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| return super.onKeyDown(keyCode, event); | return super.onKeyDown(keyCode, event); | ||||
| } | } | ||||
| @Override | |||||
| public void onBackPressed() | |||||
| { | |||||
| if(!HandleBackKeyEvent(2)) | |||||
| super.onBackPressed(); | |||||
| } | |||||
| @Override | @Override | ||||
| protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { | ||||
| super.onActivityResult(requestCode, resultCode, data); | super.onActivityResult(requestCode, resultCode, data); | ||||
| } | } | ||||
| @Override | |||||
| public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | |||||
| super.onRequestPermissionsResult(requestCode, permissions, grantResults); | |||||
| if (requestCode == PERMISSION_LOCATION_REQUEST_CODE) { | |||||
| int granted = 0; | |||||
| for(int i = 0; i < permissions.length; i++) | |||||
| { | |||||
| boolean b = grantResults[i] == PackageManager.PERMISSION_GRANTED; | |||||
| Log.i(ID_TAG, String.format("请求权限: %s -> %s", permissions[i], b ? "通过" : "拒绝")); | |||||
| if(b) | |||||
| granted++; | |||||
| } | |||||
| if (granted < permissions.length) { | |||||
| Toast.makeText(this, "需要定位权限", Toast.LENGTH_LONG).show(); | |||||
| NSContextUtils.RequestLocationPermission(this, FullscreenActivity.PERMISSION_LOCATION_REQUEST_CODE); | |||||
| } | |||||
| } | |||||
| } | |||||
| private NSPreference GetPreference() | |||||
| { | |||||
| if(null == preference) | |||||
| preference = new NSPreference(this); | |||||
| return preference; | |||||
| } | |||||
| private boolean IsRecordUrl() | |||||
| { | |||||
| return GetPreference().GetBool(NSPreference.OPEN_LAST_URL); | |||||
| } | |||||
| private String GetHomeUrl() | |||||
| { | |||||
| String url = null; | |||||
| if(IsRecordUrl()) | |||||
| url = GetPreference().GetString(NSPreference.LAST_ACCESS_URL, NSConstants.AppHomeUrl()); | |||||
| if(StrUtil.isBlank(url)) | |||||
| url = NSConstants.AppHomeUrl(); | |||||
| return url; | |||||
| } | |||||
| private WebView GetWebView() | |||||
| { | |||||
| return mAgentWeb.getWebCreator().getWebView(); | |||||
| } | |||||
| private void DumpMainUrl(String url) | |||||
| { | |||||
| if(StrUtil.isNotEmpty(url)) | |||||
| GetPreference().SetString(NSPreference.LAST_ACCESS_URL, url); | |||||
| else | |||||
| GetPreference().Remove(NSPreference.LAST_ACCESS_URL); | |||||
| Log.d(ID_TAG, "Dump URL: " + url); | |||||
| } | |||||
| private void DumpCookie() | |||||
| { | |||||
| String url = NSStr.GetUrlPart(NSConstants.AppHomeUrl()); | |||||
| String cookies = AgentWebConfig.getCookiesByUrl(url); | |||||
| GetPreference().SetString(NSPreference.COOKIES, cookies); | |||||
| Log.d(ID_TAG, "Dump cookie: " + "url=" + url + ", cookie=" + cookies); | |||||
| } | |||||
| private void RestoreCookie() | |||||
| { | |||||
| String cookies = GetPreference().GetString(NSPreference.COOKIES); | |||||
| if(StrUtil.isNotBlank(cookies)) | |||||
| { | |||||
| String url = NSStr.GetUrlPart(NSConstants.AppHomeUrl()); | |||||
| AgentWebConfig.syncCookie(url, cookies); | |||||
| Log.d(ID_TAG, "Restore cookie: " + "url=" + url + ", cookie=" + cookies); | |||||
| } | |||||
| } | |||||
| private boolean IsInDefaultPage() | |||||
| { | |||||
| return IsDefaultPage(m_lastUrl); | |||||
| } | |||||
| private boolean IsDefaultPage(String url) | |||||
| { | |||||
| return NSConstants.AppHomeUrl().equals(url); | |||||
| } | |||||
| private void History() | |||||
| { | |||||
| WebBackForwardList webBackForwardList = GetWebView().copyBackForwardList(); | |||||
| for(int i = 0; i < webBackForwardList.getSize(); i++) | |||||
| { | |||||
| WebHistoryItem item = webBackForwardList.getItemAtIndex(i); | |||||
| Log.d(ID_TAG, StrUtil.format("{}: {} {}", i, item.getTitle(), item.getUrl())); | |||||
| } | |||||
| } | |||||
| public void SetIndicatorVisible(boolean on) | |||||
| { | |||||
| m_progressIndicator.setVisibility(on ? View.VISIBLE : View.GONE); | |||||
| } | |||||
| private boolean HandleBackKeyEvent(int from) | |||||
| { | |||||
| if(!IsRecordUrl()) | |||||
| return false; | |||||
| WebView webView = GetWebView(); | |||||
| if(null == webView) | |||||
| return false; | |||||
| boolean inDefaultPage = IsInDefaultPage(); | |||||
| //System.err.println(m_lastUrl +" -> " + inDefaultPage + " = " + webView.canGoBack()); | |||||
| if(inDefaultPage) | |||||
| { | |||||
| if(from == 1 && !webView.canGoBack()) | |||||
| { | |||||
| finish(); | |||||
| return true; | |||||
| } | |||||
| else | |||||
| return false; | |||||
| } | |||||
| if (!webView.canGoBack() && !inDefaultPage) { | |||||
| webView.loadUrl(NSConstants.AppHomeUrl()); | |||||
| webView.clearHistory(); | |||||
| //History(); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,27 @@ | |||||
| package com.nsgk.ruralWeb; | |||||
| import android.os.Bundle; | |||||
| import androidx.appcompat.app.AppCompatActivity; | |||||
| import com.nsgk.ruralWeb.ui.SettingsFragment; | |||||
| public class SettingsActivity extends AppCompatActivity | |||||
| { | |||||
| private static final String ID_TAG = SettingsActivity.class.getName(); | |||||
| @Override | |||||
| protected void onCreate(Bundle savedInstanceState) | |||||
| { | |||||
| super.onCreate(savedInstanceState); | |||||
| setTitle(R.string.settings_name); | |||||
| getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit(); | |||||
| } | |||||
| @Override | |||||
| protected void onDestroy() { | |||||
| super.onDestroy(); | |||||
| } | |||||
| } | |||||
| @@ -9,10 +9,13 @@ import android.view.animation.Animation; | |||||
| import android.view.animation.AnimationUtils; | import android.view.animation.AnimationUtils; | ||||
| import android.widget.ImageView; | import android.widget.ImageView; | ||||
| import android.widget.LinearLayout; | import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | |||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||
| import com.nsgk.ruralWeb.sys.NSConstants; | |||||
| public class WelcomeActivity extends AppCompatActivity { | public class WelcomeActivity extends AppCompatActivity { | ||||
| private ImageView imageView; | private ImageView imageView; | ||||
| private LinearLayout llcenter; | private LinearLayout llcenter; | ||||
| @@ -36,12 +39,38 @@ public class WelcomeActivity extends AppCompatActivity { | |||||
| } | } | ||||
| }; | }; | ||||
| private String GetTrimString(String str) | |||||
| { | |||||
| if(null == str || str.isEmpty()) | |||||
| return null; | |||||
| str = str.trim(); | |||||
| if(str.isEmpty()) | |||||
| return null; | |||||
| return str; | |||||
| } | |||||
| @Override | @Override | ||||
| protected void onCreate(Bundle savedInstanceState) { | protected void onCreate(Bundle savedInstanceState) { | ||||
| super.onCreate(savedInstanceState); | super.onCreate(savedInstanceState); | ||||
| this.setContentView(R.layout.activity_welcome); | this.setContentView(R.layout.activity_welcome); | ||||
| imageView = findViewById(R.id.imageView); | imageView = findViewById(R.id.imageView); | ||||
| llcenter = findViewById(R.id.llcenter); | llcenter = findViewById(R.id.llcenter); | ||||
| // 动态设置启动页版权信息 | |||||
| String text = GetTrimString(NSConstants.AppCopyright()); | |||||
| if(null != text) | |||||
| { | |||||
| TextView copyrightText = findViewById(R.id.welcome_text_copyright); | |||||
| copyrightText.setText(text); | |||||
| } | |||||
| // 动态设置启动页厂商名称 | |||||
| text = GetTrimString(NSConstants.AppVendor()); | |||||
| if(null != text) | |||||
| { | |||||
| TextView vendorText = findViewById(R.id.welcome_text_vendor); | |||||
| vendorText.setText(text); | |||||
| } | |||||
| Animation animation = AnimationUtils.loadAnimation(this, R.anim.img_anim); | Animation animation = AnimationUtils.loadAnimation(this, R.anim.img_anim); | ||||
| animation.start(); | animation.start(); | ||||
| handler.sendEmptyMessageDelayed(1, 1000); | handler.sendEmptyMessageDelayed(1, 1000); | ||||
| @@ -0,0 +1,23 @@ | |||||
| package com.nsgk.ruralWeb.enums; | |||||
| public final class NSEnums | |||||
| { | |||||
| public final class LocationMode | |||||
| { | |||||
| public static final String SYSTEM = "system"; | |||||
| public static final String GAODE = "gaode"; | |||||
| public static final String REALTIME = "realtime"; | |||||
| public static final String H5 = "h5"; | |||||
| private LocationMode() {} | |||||
| } | |||||
| public final class LocationProvider | |||||
| { | |||||
| public static final String AUTO = "auto"; | |||||
| private LocationProvider() {} | |||||
| } | |||||
| private NSEnums() {} | |||||
| } | |||||
| @@ -0,0 +1,462 @@ | |||||
| package com.nsgk.ruralWeb.location; | |||||
| import android.annotation.SuppressLint; | |||||
| import android.content.Context; | |||||
| import android.os.HandlerThread; | |||||
| import android.os.Looper; | |||||
| import android.util.Log; | |||||
| import com.amap.api.location.AMapLocation; | |||||
| import com.amap.api.location.AMapLocationClient; | |||||
| import com.amap.api.location.AMapLocationClientOption; | |||||
| import com.amap.api.location.AMapLocationListener; | |||||
| import com.amap.api.location.CoordinateConverter; | |||||
| import com.amap.api.location.DPoint; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| // 主线程调用, 无线程安全 | |||||
| public class NSAMapLocation implements AMapLocationListener | |||||
| { | |||||
| // 定位场景: 默认无场景 | |||||
| public static final int FLAG_PURPOSE_SIGNIN = 1; // 签到 | |||||
| public static final int FLAG_PURPOSE_TRANSPORT = 1 << 1; // 出行 | |||||
| public static final int FLAG_PURPOSE_SPORT = 1 << 2; // 运动 | |||||
| // 定位模式 | |||||
| public static final int FLAG_MODE_HIGH = 1 << 3; // 高精度模式 | |||||
| public static final int FLAG_MODE_SAVING = 1 << 4; // 低功耗模式 | |||||
| public static final int FLAG_MODE_SENSORS = 1 << 5; // 仅设备模式 | |||||
| // 获取一次定位结果: 默认为false | |||||
| public static final int FLAG_ONCE = 1 << 6; // true | |||||
| // 获取最近3s内精度最高的一次定位结果: 默认为false | |||||
| public static final int FLAG_ONCE_LATEST = 1 << 7; // true | |||||
| // 设置是否返回地址信息: 默认返回地址信息 | |||||
| public static final int FLAG_NO_NEED_ADDRESS = 1 << 8; // false | |||||
| // 设置是否允许模拟位置: 默认为true | |||||
| public static final int FLAG_MOCK_DISABLE = 1 << 9; // false | |||||
| // 关闭缓存机制: 默认缓存 | |||||
| public static final int FLAG_CACHE_DISABLE = 1 << 10; // false | |||||
| // 线程 | |||||
| public static final int FLAG_BUILTIN_THREAD = 1 << 11; // 使用内建线程 | |||||
| public static final int FLAG_CUSTOM_THREAD = 1 << 12; // 使用外部线程 | |||||
| public static final int FLAG_MAIN_THREAD = 1 << 13; // 使用主线程 | |||||
| public static final int DEFAULT_FLAG = FLAG_MODE_HIGH | FLAG_NO_NEED_ADDRESS; | |||||
| public static final int OPTION_INTERVAL = 1; // 定位间隔(毫秒) 默认1000 | |||||
| public static final int OPTION_HTTP_TIMEOUT = 2; // 定位间隔(毫秒) 默认20000 | |||||
| private static final int DEFAULT_INTERVAL = 1000; | |||||
| private static final int DEFAULT_HTTP_TIMEOUT = 20000; | |||||
| private static final String ID_TAG = NSSystemLocation.class.getName(); | |||||
| private final Object m_lock = new Object(); | |||||
| //声明AMapLocationClient类对象 | |||||
| public AMapLocationClient mLocationClient = null; | |||||
| //声明定位回调监听器 | |||||
| public AMapLocationListener mLocationListener = this; | |||||
| //声明AMapLocationClientOption对象 | |||||
| public AMapLocationClientOption mLocationOption = null; | |||||
| private final Context m_context; | |||||
| private int m_flag = DEFAULT_FLAG; | |||||
| private boolean m_isPreInit = false; | |||||
| private NSLocationInfo m_lastLocation = null; | |||||
| private HandlerThread m_thread = null; | |||||
| private Looper m_customLooper = null; | |||||
| private AtomicInteger m_readOnce = new AtomicInteger(-1); | |||||
| private Map<Integer, Object> m_options = new HashMap<>(); | |||||
| public NSAMapLocation(Context context) | |||||
| { | |||||
| m_context = context; | |||||
| } | |||||
| public boolean Init(int flag) | |||||
| { | |||||
| if(IsInitialized()) | |||||
| { | |||||
| Log.e(ID_TAG, "定位已初始化"); | |||||
| return true; | |||||
| } | |||||
| try | |||||
| { | |||||
| m_readOnce.set(-1); | |||||
| m_flag = flag < 0 ? DEFAULT_FLAG : flag; | |||||
| // 在构造AMapLocationClient 之前必须进行合规检查,设置接口之前保证隐私政策合规 | |||||
| PreInit(); | |||||
| Context applicationContext = m_context.getApplicationContext(); | |||||
| //初始化定位 | |||||
| if(HasFlag(FLAG_BUILTIN_THREAD)) | |||||
| { | |||||
| m_thread = new HandlerThread("AMap内建定位"); | |||||
| m_thread.start(); | |||||
| mLocationClient = new AMapLocationClient(m_thread.getLooper(), applicationContext); | |||||
| Log.d(ID_TAG, "定位使用内建线程: " + m_thread.getId()); | |||||
| } | |||||
| else if(HasFlag(FLAG_CUSTOM_THREAD)) | |||||
| { | |||||
| if(null == m_customLooper) | |||||
| { | |||||
| throw new RuntimeException("请先传入线程Looper"); | |||||
| } | |||||
| mLocationClient = new AMapLocationClient(m_customLooper, applicationContext); | |||||
| Log.d(ID_TAG, "定位使用用户指定线程: " + m_customLooper.getThread().getId()); | |||||
| } | |||||
| else if(HasFlag(FLAG_MAIN_THREAD)) | |||||
| { | |||||
| Looper mainLooper = Looper.getMainLooper(); | |||||
| mLocationClient = new AMapLocationClient(mainLooper, applicationContext); | |||||
| Log.d(ID_TAG, "定位使用主线程: " + mainLooper.getThread().getId()); | |||||
| } | |||||
| else | |||||
| { | |||||
| mLocationClient = new AMapLocationClient(applicationContext); | |||||
| Log.d(ID_TAG, "定位使用当前线程: " + Thread.currentThread().getId()); | |||||
| } | |||||
| //设置定位回调监听 | |||||
| mLocationClient.setLocationListener(mLocationListener); | |||||
| InitOptions(); | |||||
| mLocationClient.setLocationOption(mLocationOption); | |||||
| //V6.4.9版本起 client设置逆地理回调 | |||||
| /* mLocationClient.setReGeoLocationCallback(new IReGeoLocationCallback() | |||||
| { | |||||
| @Override | |||||
| public void onReGeoLocation(AMapLocation reGeoLocation) | |||||
| { | |||||
| Log.i(ID_TAG, reGeoLocation.toStr()); | |||||
| } | |||||
| });*/ | |||||
| Log.i(ID_TAG, "定位初始化完成"); | |||||
| return true; | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| mLocationClient = null; | |||||
| return false; | |||||
| } | |||||
| } | |||||
| public void SetThread(Looper looper) | |||||
| { | |||||
| CheckInitialization(false); | |||||
| m_customLooper = looper; | |||||
| } | |||||
| public void SetThread(HandlerThread thread) | |||||
| { | |||||
| CheckInitialization(false); | |||||
| m_customLooper = thread.getLooper(); | |||||
| } | |||||
| public boolean IsRunning() | |||||
| { | |||||
| return IsInitialized() && mLocationClient.isStarted(); | |||||
| } | |||||
| public void Start() | |||||
| { | |||||
| Run(-1); | |||||
| } | |||||
| public void Stop() | |||||
| { | |||||
| CheckInitialization(true); | |||||
| //设置场景模式后最好调用一次stop,再调用start以保证场景模式生效 | |||||
| mLocationClient.stopLocation(); | |||||
| Log.i(ID_TAG, "高德定位停止"); | |||||
| } | |||||
| public void Shutdown() | |||||
| { | |||||
| if(!IsInitialized()) | |||||
| { | |||||
| Log.e(ID_TAG, "请先初始化"); | |||||
| return; | |||||
| } | |||||
| TerminateRead(); | |||||
| Stop(); | |||||
| mLocationClient.onDestroy(); | |||||
| mLocationClient = null; | |||||
| if(null != m_thread) | |||||
| { | |||||
| m_thread.quit(); | |||||
| m_thread = null; | |||||
| Log.d(ID_TAG, "高德定位内建线程结束"); | |||||
| } | |||||
| Log.i(ID_TAG, "高德定位销毁"); | |||||
| } | |||||
| public void PreInit() | |||||
| { | |||||
| if(m_isPreInit) | |||||
| return; | |||||
| Context applicationContext = m_context.getApplicationContext(); | |||||
| /** 设置包含隐私政策,并展示用户授权弹窗 <b>必须在AmapLocationClient实例化之前调用</b> | |||||
| * | |||||
| * @param context | |||||
| * @param isContains: 是隐私权政策是否包含高德开平隐私权政策 true是包含 | |||||
| * @param isShow: 隐私权政策是否弹窗展示告知用户 true是展示 | |||||
| * @since 5.6.0 | |||||
| */ | |||||
| AMapLocationClient.updatePrivacyShow(applicationContext, true, true); | |||||
| /** | |||||
| * 设置是否同意用户授权政策 <b>必须在AmapLocationClient实例化之前调用</b> | |||||
| * @param context | |||||
| * @param isAgree:隐私权政策是否取得用户同意 true是用户同意 | |||||
| * | |||||
| * @since 5.6.0 | |||||
| */ | |||||
| AMapLocationClient.updatePrivacyAgree(applicationContext, true); | |||||
| Log.i(ID_TAG, "定位预初始化"); | |||||
| m_isPreInit = true; | |||||
| } | |||||
| public boolean IsInitialized() | |||||
| { | |||||
| return null != mLocationClient; | |||||
| } | |||||
| private void CheckInitialization(boolean inited) | |||||
| { | |||||
| if(IsInitialized() != inited) | |||||
| { | |||||
| String msg = inited ? "定位未初始化" : "定位已初始化"; | |||||
| Log.e(ID_TAG, msg); | |||||
| throw new RuntimeException(msg); | |||||
| } | |||||
| } | |||||
| private boolean HasFlag(int f) | |||||
| { | |||||
| return (m_flag & f) != 0; | |||||
| } | |||||
| public void SetOption(int type, Object value) | |||||
| { | |||||
| CheckInitialization(false); | |||||
| m_options.put(type, value); | |||||
| } | |||||
| @SuppressLint("unchecked") | |||||
| private <T> T GetOption(int type, T def) | |||||
| { | |||||
| Object o = m_options.get(type); | |||||
| if(null == o) | |||||
| return def; | |||||
| return (T) o; | |||||
| } | |||||
| private void InitOptions() | |||||
| { | |||||
| //初始化AMapLocationClientOption对象 | |||||
| mLocationOption = new AMapLocationClientOption(); | |||||
| /** | |||||
| * 设置定位场景,目前支持三种场景(签到、出行、运动,默认无场景) | |||||
| */ | |||||
| if(HasFlag(FLAG_PURPOSE_SIGNIN)) | |||||
| mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.SignIn); | |||||
| else if(HasFlag(FLAG_PURPOSE_TRANSPORT)) | |||||
| mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Transport); | |||||
| else if(HasFlag(FLAG_PURPOSE_SPORT)) | |||||
| mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Sport); | |||||
| //设置定位模式为AMapLocationMode.Hight_Accuracy,高精度模式。 | |||||
| if(HasFlag(FLAG_MODE_HIGH)) | |||||
| mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); | |||||
| //设置定位模式为AMapLocationMode.Battery_Saving,低功耗模式。 | |||||
| else if(HasFlag(FLAG_MODE_SAVING)) | |||||
| mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Battery_Saving); | |||||
| //设置定位模式为AMapLocationMode.Device_Sensors,仅设备模式。 | |||||
| else if(HasFlag(FLAG_MODE_SENSORS)) | |||||
| mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Device_Sensors); | |||||
| //获取一次定位结果: | |||||
| //该方法默认为false。 | |||||
| if(HasFlag(FLAG_ONCE)) | |||||
| mLocationOption.setOnceLocation(true); | |||||
| //获取最近3s内精度最高的一次定位结果: | |||||
| //设置setOnceLocationLatest(boolean b)接口为true,启动定位时SDK会返回最近3s内精度最高的一次定位结果。如果设置其为true,setOnceLocation(boolean b)接口也会被设置为true,反之不会,默认为false。 | |||||
| if(HasFlag(FLAG_ONCE_LATEST)) | |||||
| mLocationOption.setOnceLocationLatest(true); | |||||
| //设置定位间隔,单位毫秒,默认为2000ms,最低1000ms。 | |||||
| int interval = GetOption(OPTION_INTERVAL, DEFAULT_INTERVAL); | |||||
| mLocationOption.setInterval(interval); | |||||
| //设置是否返回地址信息(默认返回地址信息) | |||||
| if(HasFlag(FLAG_NO_NEED_ADDRESS)) | |||||
| mLocationOption.setNeedAddress(false); | |||||
| //设置是否允许模拟位置,默认为true,允许模拟位置 | |||||
| if(HasFlag(FLAG_MOCK_DISABLE)) | |||||
| mLocationOption.setMockEnable(false); | |||||
| //单位是毫秒,默认30000毫秒,建议超时时间不要低于8000毫秒。 | |||||
| int timeout = GetOption(OPTION_HTTP_TIMEOUT, DEFAULT_HTTP_TIMEOUT); | |||||
| mLocationOption.setHttpTimeOut(timeout); | |||||
| //关闭缓存机制 | |||||
| if(HasFlag(FLAG_CACHE_DISABLE)) | |||||
| mLocationOption.setLocationCacheEnable(false); | |||||
| } | |||||
| private void Run(int num) | |||||
| { | |||||
| CheckInitialization(true); | |||||
| m_readOnce.set(num); | |||||
| //设置场景模式后最好调用一次stop,再调用start以保证场景模式生效 | |||||
| mLocationClient.stopLocation(); | |||||
| mLocationClient.startLocation(); | |||||
| Log.i(ID_TAG, "定位开始"); | |||||
| } | |||||
| /* | |||||
| 在调用线程中执行 | |||||
| */ | |||||
| @Override | |||||
| public void onLocationChanged(AMapLocation amapLocation) | |||||
| { | |||||
| Log.d(ID_TAG, "AMap定位回调线程: " + Thread.currentThread().getId() + " -> " + amapLocation); | |||||
| SetLastLocation(amapLocation); | |||||
| if(null != amapLocation) | |||||
| { | |||||
| if(m_readOnce.get() > 0) | |||||
| { | |||||
| m_readOnce.decrementAndGet(); | |||||
| if(m_readOnce.get() == 0) | |||||
| { | |||||
| synchronized(m_lock) | |||||
| { | |||||
| m_lock.notifyAll(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| private double[] Convert(double longitude, double latitude) | |||||
| { | |||||
| try | |||||
| { | |||||
| //初始化坐标转换类 | |||||
| CoordinateConverter converter = new CoordinateConverter(m_context.getApplicationContext()); | |||||
| converter.from(CoordinateConverter.CoordType.GPS); | |||||
| //设置需要转换的坐标 | |||||
| converter.coord(new DPoint(latitude, longitude)); | |||||
| //转换成高德坐标 | |||||
| DPoint destPoint = converter.convert(); | |||||
| double dx = destPoint.getLongitude() - longitude; | |||||
| double dy = destPoint.getLatitude() - latitude; | |||||
| longitude = longitude - dx; | |||||
| latitude = latitude - dy; | |||||
| return new double[]{ | |||||
| longitude, latitude | |||||
| }; | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| private void SetLastLocation(AMapLocation amapLocation) | |||||
| { | |||||
| if(null != amapLocation) | |||||
| { | |||||
| double longitude = amapLocation.getLongitude(); | |||||
| double latitude = amapLocation.getLatitude(); | |||||
| double[] coord = Convert(longitude, latitude); | |||||
| longitude = coord[0]; | |||||
| latitude = coord[1]; | |||||
| m_lastLocation = new NSLocationInfo(amapLocation.getProvider(), longitude, latitude); | |||||
| } | |||||
| else | |||||
| { | |||||
| m_lastLocation = null; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 同步调用, 会锁住线程 | |||||
| */ | |||||
| public NSLocationInfo Read(int count, int timeout) | |||||
| { | |||||
| CheckInitialization(true); | |||||
| /* if(IsRunning()) | |||||
| { | |||||
| throw new RuntimeException("请先停止定位"); | |||||
| }*/ | |||||
| if(timeout < 0) | |||||
| timeout = 0; | |||||
| Log.d(ID_TAG, "AMap定位线程: " + Thread.currentThread().getId() + ", 超时设置: " + timeout); | |||||
| synchronized(m_lock) | |||||
| { | |||||
| try | |||||
| { | |||||
| CleanLastLocation(); | |||||
| Run(count); | |||||
| m_lock.wait(timeout); | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| finally | |||||
| { | |||||
| Stop(); | |||||
| } | |||||
| } | |||||
| return m_lastLocation; | |||||
| } | |||||
| public NSLocationInfo GetLastLocation() | |||||
| { | |||||
| return m_lastLocation; | |||||
| } | |||||
| public void TerminateRead() | |||||
| { | |||||
| synchronized(m_lock) | |||||
| { | |||||
| Log.i(ID_TAG, "AMap中止定位"); | |||||
| m_lock.notifyAll(); | |||||
| } | |||||
| } | |||||
| public void CleanLastLocation() | |||||
| { | |||||
| m_lastLocation = null; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,84 @@ | |||||
| package com.nsgk.ruralWeb.location; | |||||
| import android.annotation.SuppressLint; | |||||
| import android.app.Activity; | |||||
| import android.content.Context; | |||||
| import android.location.Criteria; | |||||
| import android.location.Location; | |||||
| import android.location.LocationManager; | |||||
| import android.os.Build; | |||||
| import android.os.CancellationSignal; | |||||
| import android.util.Log; | |||||
| import android.widget.Toast; | |||||
| import com.nsgk.ruralWeb.sys.NSConstants; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.CompletableFuture; | |||||
| import java.util.concurrent.TimeUnit; | |||||
| public class NSLastKnownLocation | |||||
| { | |||||
| private static final String ID_TAG = NSLastKnownLocation.class.getName(); | |||||
| private final Context m_context; | |||||
| private NSLocationInfo m_lastLocation = null; | |||||
| public NSLastKnownLocation(Context context) | |||||
| { | |||||
| m_context = context; | |||||
| } | |||||
| @SuppressLint("MissingPermission") | |||||
| public NSLocationInfo Read(String type) | |||||
| { | |||||
| NSLocationInfo loc = null; | |||||
| try | |||||
| { | |||||
| LocationManager lm = (LocationManager) m_context.getSystemService(Context.LOCATION_SERVICE); | |||||
| List<String> allProviders = lm.getAllProviders(); | |||||
| Log.i(ID_TAG, "使用最近所有定位提供器: " + allProviders); | |||||
| Map<String, NSLocationInfo> map = new HashMap<>(); | |||||
| for(String provider : allProviders) | |||||
| { | |||||
| Location lastKnownLocation = lm.getLastKnownLocation(provider); | |||||
| if(null == lastKnownLocation) | |||||
| continue; | |||||
| NSLocationInfo l = new NSLocationInfo(provider, lastKnownLocation.getLongitude(), lastKnownLocation.getLatitude()); | |||||
| map.put(provider, l); | |||||
| } | |||||
| Log.i(ID_TAG, "最近定位提供器结果: " + map); | |||||
| if(null != type && !type.isEmpty() && map.containsKey(type)) | |||||
| loc = map.get(type); | |||||
| else if(map.containsKey(LocationManager.GPS_PROVIDER)) | |||||
| loc = map.get(LocationManager.GPS_PROVIDER); | |||||
| else if(map.containsKey(LocationManager.NETWORK_PROVIDER)) | |||||
| loc = map.get(LocationManager.NETWORK_PROVIDER); | |||||
| else if(map.containsKey(NSConstants.FUSED_PROVIDER)) | |||||
| loc = map.get(NSConstants.FUSED_PROVIDER); | |||||
| else if(map.containsKey(LocationManager.PASSIVE_PROVIDER)) | |||||
| loc = map.get(LocationManager.PASSIVE_PROVIDER); | |||||
| if(null == loc) | |||||
| Log.i(ID_TAG, "所有提供器无最近定位"); | |||||
| else | |||||
| { | |||||
| Log.i(ID_TAG, "使用提供器最近定位: " + loc.provider); | |||||
| m_lastLocation = loc; | |||||
| } | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return loc; | |||||
| } | |||||
| public NSLocationInfo GetLastLocation() | |||||
| { | |||||
| return m_lastLocation; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| package com.nsgk.ruralWeb.location; | |||||
| public final class NSLocationInfo | |||||
| { | |||||
| public final String provider; | |||||
| public final double longitude; | |||||
| public final double latitude; | |||||
| public NSLocationInfo(String provider, double longitude, double latitude) | |||||
| { | |||||
| this.provider = provider; | |||||
| this.longitude = longitude; | |||||
| this.latitude = latitude; | |||||
| } | |||||
| @Override | |||||
| public String toString() | |||||
| { | |||||
| return "定位信息: " + | |||||
| "提供器=" + provider + | |||||
| "; 经度=" + longitude + | |||||
| "; 纬度=" + latitude | |||||
| ; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,380 @@ | |||||
| package com.nsgk.ruralWeb.location; | |||||
| import android.annotation.SuppressLint; | |||||
| import android.content.Context; | |||||
| import android.location.Location; | |||||
| import android.location.LocationListener; | |||||
| import android.location.LocationManager; | |||||
| import android.os.HandlerThread; | |||||
| import android.os.Looper; | |||||
| import android.util.Log; | |||||
| import androidx.annotation.NonNull; | |||||
| import com.amap.api.location.AMapLocation; | |||||
| import com.amap.api.location.AMapLocationClient; | |||||
| import com.amap.api.location.AMapLocationClientOption; | |||||
| import com.amap.api.location.AMapLocationListener; | |||||
| import com.amap.api.location.CoordinateConverter; | |||||
| import com.amap.api.location.DPoint; | |||||
| import java.util.ArrayList; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| // 主线程调用, 无线程安全 | |||||
| public class NSRealtimeLocation implements LocationListener | |||||
| { | |||||
| // 定位提供器 | |||||
| public static final int FLAG_PROVIDER_NETWORK = 1; // 网络定位 | |||||
| public static final int FLAG_PROVIDER_GPS = 1 << 1; // GPS定位 | |||||
| // 定位提供器优先级 | |||||
| public static final int FLAG_PRIORITY_NETWORK = 1 << 2; // 网络定位优先 | |||||
| public static final int FLAG_PRIORITY_GPS = 1 << 3; // GPS定位优先 | |||||
| // 线程 | |||||
| public static final int FLAG_BUILTIN_THREAD = 1 << 4; // 使用内建线程 | |||||
| public static final int FLAG_CUSTOM_THREAD = 1 << 5; // 使用外部线程 | |||||
| public static final int FLAG_MAIN_THREAD = 1 << 6; // 使用主线程 | |||||
| public static final int DEFAULT_FLAG = FLAG_PROVIDER_NETWORK | FLAG_PROVIDER_GPS | FLAG_PRIORITY_NETWORK; | |||||
| public static final int OPTION_MIN_TIME = 1; // 最小定位间隔(毫秒) 默认60000 | |||||
| public static final int OPTION_MIN_DISTANCE = 2; // 最小定位距离变化(米) 默认10 | |||||
| private static final int DEFAULT_MIN_TIME = 1000; | |||||
| private static final int DEFAULT_MIN_DISTANCE = 1; | |||||
| private static final String ID_TAG = NSRealtimeLocation.class.getName(); | |||||
| private final Object m_lock = new Object(); | |||||
| private LocationManager m_locationManager; | |||||
| private final Context m_context; | |||||
| private int m_flag = DEFAULT_FLAG; | |||||
| private NSLocationInfo m_lastLocation = null; | |||||
| private HandlerThread m_thread = null; | |||||
| private Looper m_customLooper = null; | |||||
| private AtomicInteger m_readOnce = new AtomicInteger(-1); | |||||
| private Map<Integer, Object> m_options = new HashMap<>(); | |||||
| private String m_provider = null; | |||||
| private Looper m_looper = null; | |||||
| public NSRealtimeLocation(Context context) | |||||
| { | |||||
| m_context = context; | |||||
| } | |||||
| public boolean Init(int flag) | |||||
| { | |||||
| if(IsInitialized()) | |||||
| { | |||||
| Log.e(ID_TAG, "定位已初始化"); | |||||
| return true; | |||||
| } | |||||
| if(!HasFlag(FLAG_PROVIDER_NETWORK | FLAG_PROVIDER_GPS)) | |||||
| { | |||||
| Log.e(ID_TAG, "请设置定位提供器"); | |||||
| return false; | |||||
| } | |||||
| try | |||||
| { | |||||
| m_readOnce.set(-1); | |||||
| m_flag = flag < 0 ? DEFAULT_FLAG : flag; | |||||
| m_locationManager = (LocationManager) m_context.getSystemService(Context.LOCATION_SERVICE); | |||||
| //初始化定位 | |||||
| if(HasFlag(FLAG_BUILTIN_THREAD)) | |||||
| { | |||||
| m_thread = new HandlerThread("系统实时内建定位"); | |||||
| m_thread.start(); | |||||
| m_looper = m_thread.getLooper(); | |||||
| Log.d(ID_TAG, "定位使用内建线程: " + m_thread.getId()); | |||||
| } | |||||
| else if(HasFlag(FLAG_CUSTOM_THREAD)) | |||||
| { | |||||
| if(null == m_customLooper) | |||||
| { | |||||
| throw new RuntimeException("请先传入线程Looper"); | |||||
| } | |||||
| m_looper = m_customLooper; | |||||
| Log.d(ID_TAG, "定位使用用户指定线程: " + m_customLooper.getThread().getId()); | |||||
| } | |||||
| else if(HasFlag(FLAG_MAIN_THREAD)) | |||||
| { | |||||
| Looper mainLooper = Looper.getMainLooper(); | |||||
| m_looper = mainLooper; | |||||
| Log.d(ID_TAG, "定位使用主线程: " + mainLooper.getThread().getId()); | |||||
| } | |||||
| else | |||||
| { | |||||
| m_looper = Looper.myLooper(); | |||||
| Log.d(ID_TAG, "定位使用当前线程: " + Thread.currentThread().getId()); | |||||
| } | |||||
| Log.i(ID_TAG, "定位初始化完成"); | |||||
| return true; | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| m_locationManager = null; | |||||
| return false; | |||||
| } | |||||
| } | |||||
| public void SetThread(Looper looper) | |||||
| { | |||||
| CheckInitialization(false); | |||||
| m_customLooper = looper; | |||||
| } | |||||
| public void SetThread(HandlerThread thread) | |||||
| { | |||||
| CheckInitialization(false); | |||||
| m_customLooper = thread.getLooper(); | |||||
| } | |||||
| public boolean IsRunning() | |||||
| { | |||||
| return IsInitialized() && null != m_provider; | |||||
| } | |||||
| public void Start() | |||||
| { | |||||
| Run(-1); | |||||
| } | |||||
| public void Stop() | |||||
| { | |||||
| CheckInitialization(true); | |||||
| m_locationManager.removeUpdates(this); | |||||
| m_provider = null; | |||||
| Log.i(ID_TAG, "实时定位停止"); | |||||
| } | |||||
| public void Shutdown() | |||||
| { | |||||
| if(!IsInitialized()) | |||||
| { | |||||
| Log.e(ID_TAG, "请先初始化"); | |||||
| return; | |||||
| } | |||||
| TerminateRead(); | |||||
| Stop(); | |||||
| m_locationManager = null; | |||||
| if(null != m_thread) | |||||
| { | |||||
| m_thread.quit(); | |||||
| m_thread = null; | |||||
| Log.d(ID_TAG, "实时定位内建线程结束"); | |||||
| } | |||||
| Log.i(ID_TAG, "实时定位销毁"); | |||||
| } | |||||
| public boolean IsInitialized() | |||||
| { | |||||
| return null != m_locationManager; | |||||
| } | |||||
| private void CheckInitialization(boolean inited) | |||||
| { | |||||
| if(IsInitialized() != inited) | |||||
| { | |||||
| String msg = inited ? "定位未初始化" : "定位已初始化"; | |||||
| Log.e(ID_TAG, msg); | |||||
| throw new RuntimeException(msg); | |||||
| } | |||||
| } | |||||
| private boolean HasFlag(int f) | |||||
| { | |||||
| return (m_flag & f) != 0; | |||||
| } | |||||
| public void SetOption(int type, Object value) | |||||
| { | |||||
| //CheckInitialization(false); | |||||
| m_options.put(type, value); | |||||
| } | |||||
| @SuppressLint("unchecked") | |||||
| private <T> T GetOption(int type, T def) | |||||
| { | |||||
| Object o = m_options.get(type); | |||||
| if(null == o) | |||||
| return def; | |||||
| return (T) o; | |||||
| } | |||||
| private void InitOptions() | |||||
| { | |||||
| List<String> providers = new ArrayList<>(); | |||||
| m_provider = null; | |||||
| if(HasFlag(FLAG_PROVIDER_NETWORK)) | |||||
| { | |||||
| if(m_locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) | |||||
| providers.add(LocationManager.NETWORK_PROVIDER); | |||||
| else | |||||
| Log.w(ID_TAG, "网络定位提供器未启用"); | |||||
| } | |||||
| if(HasFlag(FLAG_PROVIDER_GPS)) | |||||
| { | |||||
| if(m_locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) | |||||
| providers.add(LocationManager.GPS_PROVIDER); | |||||
| else | |||||
| Log.w(ID_TAG, "GPS定位提供器未启用"); | |||||
| } | |||||
| if(providers.isEmpty()) | |||||
| throw new RuntimeException("无定位提供器"); | |||||
| if(HasFlag(FLAG_PRIORITY_NETWORK)) | |||||
| { | |||||
| if(providers.contains(LocationManager.NETWORK_PROVIDER)) | |||||
| m_provider = LocationManager.NETWORK_PROVIDER; | |||||
| else | |||||
| m_provider = LocationManager.GPS_PROVIDER; | |||||
| } | |||||
| else if(HasFlag(FLAG_PRIORITY_GPS)) | |||||
| { | |||||
| if(providers.contains(LocationManager.GPS_PROVIDER)) | |||||
| m_provider = LocationManager.GPS_PROVIDER; | |||||
| else | |||||
| m_provider = LocationManager.NETWORK_PROVIDER; | |||||
| } | |||||
| /* else | |||||
| { | |||||
| if(providers.contains(LocationManager.NETWORK_PROVIDER)) | |||||
| m_provider = LocationManager.NETWORK_PROVIDER; | |||||
| else | |||||
| m_provider = LocationManager.GPS_PROVIDER; | |||||
| }*/ | |||||
| if(null == m_provider) | |||||
| throw new RuntimeException("未确定定位提供器"); | |||||
| Log.i(ID_TAG, "使用定位器: " + m_provider); | |||||
| } | |||||
| @SuppressLint("MissingPermission") | |||||
| private void Run(int num) | |||||
| { | |||||
| CheckInitialization(true); | |||||
| Stop(); | |||||
| m_readOnce.set(num); | |||||
| InitOptions(); | |||||
| int minTime = GetOption(OPTION_MIN_TIME, DEFAULT_MIN_TIME); | |||||
| int minDistance = GetOption(OPTION_MIN_DISTANCE, DEFAULT_MIN_DISTANCE); | |||||
| m_locationManager.requestLocationUpdates(m_provider, minTime, minDistance, this, m_looper); | |||||
| Location lastKnownLocation = m_locationManager.getLastKnownLocation(m_provider); | |||||
| SetLastLocation(lastKnownLocation); | |||||
| Log.i(ID_TAG, "定位开始"); | |||||
| } | |||||
| private void SetLastLocation(Location location) | |||||
| { | |||||
| if(null != location) | |||||
| { | |||||
| m_lastLocation = new NSLocationInfo(location.getProvider(), location.getLongitude(), location.getLatitude()); | |||||
| } | |||||
| else | |||||
| { | |||||
| m_lastLocation = null; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 同步调用, 会锁住线程 | |||||
| */ | |||||
| public NSLocationInfo Read(int count, int timeout) | |||||
| { | |||||
| CheckInitialization(true); | |||||
| /* if(IsRunning()) | |||||
| { | |||||
| throw new RuntimeException("请先停止定位"); | |||||
| }*/ | |||||
| if(timeout < 0) | |||||
| timeout = 0; | |||||
| Log.d(ID_TAG, "实时定位线程: " + Thread.currentThread().getId() + ", 超时设置: " + timeout); | |||||
| synchronized(m_lock) | |||||
| { | |||||
| try | |||||
| { | |||||
| CleanLastLocation(); | |||||
| Run(count); | |||||
| m_lock.wait(timeout); | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| finally | |||||
| { | |||||
| Stop(); | |||||
| } | |||||
| } | |||||
| return m_lastLocation; | |||||
| } | |||||
| public NSLocationInfo GetLastLocation() | |||||
| { | |||||
| return m_lastLocation; | |||||
| } | |||||
| public void CleanLastLocation() | |||||
| { | |||||
| m_lastLocation = null; | |||||
| } | |||||
| /* | |||||
| 在调用线程中执行 | |||||
| */ | |||||
| @Override | |||||
| public void onLocationChanged(@NonNull Location location) | |||||
| { | |||||
| Log.d(ID_TAG, "系统实时定位线程: " + Thread.currentThread().getId() + " -> " + location); | |||||
| SetLastLocation(location); | |||||
| //if(null != location) | |||||
| { | |||||
| if(m_readOnce.get() > 0) | |||||
| { | |||||
| m_readOnce.decrementAndGet(); | |||||
| if(m_readOnce.get() == 0) | |||||
| { | |||||
| synchronized(m_lock) | |||||
| { | |||||
| m_lock.notifyAll(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| public void TerminateRead() | |||||
| { | |||||
| synchronized(m_lock) | |||||
| { | |||||
| Log.i(ID_TAG, "系统实时中止定位"); | |||||
| m_lock.notifyAll(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,161 @@ | |||||
| package com.nsgk.ruralWeb.location; | |||||
| import android.annotation.SuppressLint; | |||||
| import android.app.Activity; | |||||
| import android.content.Context; | |||||
| import android.location.Criteria; | |||||
| import android.location.Location; | |||||
| import android.location.LocationManager; | |||||
| import android.os.Build; | |||||
| import android.os.CancellationSignal; | |||||
| import android.util.Log; | |||||
| import android.widget.Toast; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.CompletableFuture; | |||||
| import java.util.concurrent.TimeUnit; | |||||
| public class NSSystemLocation | |||||
| { | |||||
| private static final String ID_TAG = NSSystemLocation.class.getName(); | |||||
| private final Context m_context; | |||||
| private NSLocationInfo m_lastLocation = null; | |||||
| private volatile CancellationSignal m_cancellationSignal; | |||||
| private volatile CompletableFuture<Location> m_future; | |||||
| public NSSystemLocation(Context context) | |||||
| { | |||||
| m_context = context; | |||||
| } | |||||
| @SuppressLint("MissingPermission") | |||||
| public NSLocationInfo Read(String provider, int timeout) | |||||
| { | |||||
| if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R) | |||||
| { | |||||
| Log.w(ID_TAG, "获取当前定位请求Android 11+"); | |||||
| return null; | |||||
| } | |||||
| NSLocationInfo loc = null; | |||||
| try | |||||
| { | |||||
| LocationManager lm = (LocationManager) m_context.getSystemService(Context.LOCATION_SERVICE); | |||||
| if(null != provider) | |||||
| { | |||||
| if(LocationManager.GPS_PROVIDER.equals(provider)) | |||||
| { | |||||
| if(!lm.isProviderEnabled(provider)) | |||||
| { | |||||
| Log.w(ID_TAG, "GPS定位提供器未启用, 尝试网络定位器"); | |||||
| provider = LocationManager.NETWORK_PROVIDER; | |||||
| if(!lm.isProviderEnabled(provider)) | |||||
| { | |||||
| Log.w(ID_TAG, "网络定位提供器也未启用"); | |||||
| provider = null; | |||||
| } | |||||
| } | |||||
| } | |||||
| else if(LocationManager.NETWORK_PROVIDER.equals(provider)) | |||||
| { | |||||
| Log.w(ID_TAG, "网络定位提供器未启用, 尝试GPS定位器"); | |||||
| if(!lm.isProviderEnabled(provider)) | |||||
| { | |||||
| provider = LocationManager.GPS_PROVIDER; | |||||
| if(!lm.isProviderEnabled(provider)) | |||||
| { | |||||
| Log.w(ID_TAG, "GPS定位提供器也未启用"); | |||||
| provider = null; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if(null == provider) | |||||
| { | |||||
| Criteria criteria = new Criteria(); | |||||
| criteria.setAccuracy(Criteria.ACCURACY_FINE); // 高精度 | |||||
| // criteria.setPowerRequirement(Criteria.POWER_HIGH); | |||||
| // criteria.setAltitudeRequired(false); | |||||
| provider = lm.getBestProvider(criteria, true); | |||||
| } | |||||
| Log.i(ID_TAG, "使用高精度定位提供器: " + provider); | |||||
| if(null == provider) | |||||
| { | |||||
| Log.i(ID_TAG, "无法获取高精度定位提供器"); | |||||
| return null; | |||||
| } | |||||
| m_cancellationSignal = new CancellationSignal(); | |||||
| m_future = new CompletableFuture<>(); | |||||
| lm.getCurrentLocation(provider, m_cancellationSignal, m_context.getMainExecutor(), m_future::complete); | |||||
| Location location; | |||||
| if(timeout > 0) | |||||
| location = m_future.get(timeout, TimeUnit.MILLISECONDS); | |||||
| else | |||||
| location = m_future.get(); | |||||
| m_cancellationSignal = null; | |||||
| m_future = null; | |||||
| if(null == location) | |||||
| { | |||||
| Log.i(ID_TAG, "无法使用高精度提供器定位"); | |||||
| return null; | |||||
| } | |||||
| loc = new NSLocationInfo(provider, location.getLongitude(), location.getLatitude()); | |||||
| Log.i(ID_TAG, "使用高精度提供器获取定位: " + loc); | |||||
| m_lastLocation = loc; | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| finally | |||||
| { | |||||
| m_cancellationSignal = null; | |||||
| m_future = null; | |||||
| } | |||||
| return loc; | |||||
| } | |||||
| private void ShowToast(final String message, int duration) | |||||
| { | |||||
| ((Activity)m_context).runOnUiThread((new Runnable() { | |||||
| @Override | |||||
| public void run() | |||||
| { | |||||
| Toast.makeText(m_context, message, duration).show(); | |||||
| } | |||||
| })); | |||||
| } | |||||
| public void TerminateRead() | |||||
| { | |||||
| CancellationSignal signal = m_cancellationSignal; | |||||
| if(null != signal && !signal.isCanceled()) | |||||
| { | |||||
| Log.i(ID_TAG, "系统中止定位: 1. 取消定位"); | |||||
| signal.cancel(); | |||||
| } | |||||
| CompletableFuture<Location> future = m_future; | |||||
| if(null != future && !future.isCancelled()) | |||||
| { | |||||
| Log.i(ID_TAG, "系统中止定位: 2. 取消等待结果"); | |||||
| future.cancel(true); | |||||
| } | |||||
| } | |||||
| public void Shutdown() | |||||
| { | |||||
| Log.i(ID_TAG, "销毁系统定位"); | |||||
| TerminateRead(); | |||||
| } | |||||
| public NSLocationInfo GetLastLocation() | |||||
| { | |||||
| return m_lastLocation; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| package com.nsgk.ruralWeb.sys; | |||||
| import android.location.LocationManager; | |||||
| import com.nsgk.ruralWeb.BuildConfig; | |||||
| import com.nsgk.ruralWeb.enums.NSEnums; | |||||
| public final class NSConstants | |||||
| { | |||||
| /** | |||||
| * App首页链接地址 | |||||
| * 可以在 /gradle.properties 里配置 appHomeUrl=链接地址, 不要携带双引号 | |||||
| * 也可以在命令行添加 -PappHomeUrl="链接地址", 双引号可携带也可不携带 | |||||
| */ | |||||
| public static String AppHomeUrl() | |||||
| { | |||||
| return BuildConfig.APP_HOME_URL; | |||||
| } | |||||
| /** | |||||
| * App启动页版权文本 | |||||
| * 可以在 /gradle.properties 里配置 appCopyright=文本, 不要携带双引号 | |||||
| * 也可以在命令行添加 -PappCopyright="文本", 双引号可携带也可不携带 | |||||
| */ | |||||
| public static String AppCopyright() | |||||
| { | |||||
| return BuildConfig.APP_COMPYRIGHT; | |||||
| } | |||||
| /** | |||||
| * App启动页厂商文本 | |||||
| * 可以在 /gradle.properties 里配置 appVendor=文本, 不要携带双引号 | |||||
| * 也可以在命令行添加 -PappVendor="文本", 双引号可携带也可不携带 | |||||
| */ | |||||
| public static String AppVendor() | |||||
| { | |||||
| return BuildConfig.APP_VENDOR; | |||||
| } | |||||
| /** | |||||
| * App图标 | |||||
| */ | |||||
| public static String AppIcon() | |||||
| { | |||||
| return BuildConfig.APP_ICON; | |||||
| } | |||||
| /** | |||||
| * App更新下载地址 | |||||
| * 可以在 /gradle.properties 里配置 appUpdateUrl=链接地址, 不要携带双引号 | |||||
| * 也可以在命令行添加 -PappUpdateUrl="链接地址", 双引号可携带也可不携带 | |||||
| */ | |||||
| public static String AppUpdateUrl() | |||||
| { | |||||
| return BuildConfig.APP_UPDATE_URL; | |||||
| } | |||||
| public static boolean IsHttps() | |||||
| { | |||||
| return AppHomeUrl().startsWith("https://"); | |||||
| } | |||||
| public static boolean IsDebug() | |||||
| { | |||||
| return BuildConfig.DEBUG; | |||||
| } | |||||
| public static boolean IsAMapLocationEnabled() | |||||
| { | |||||
| return true; | |||||
| } | |||||
| // 偏好默认值 | |||||
| public static final String DEFAULT_LOCATION_MODE = NSEnums.LocationMode.REALTIME; | |||||
| public static final int DEFAULT_LOCATION_GAODE_INTERVAL = 1000; | |||||
| public static final int DEFAULT_LOCATION_GAODE_READ_COUNT = 1; | |||||
| public static final String DEFAULT_LOCATION_PROVIDER = LocationManager.GPS_PROVIDER; | |||||
| public static final int DEFAULT_LOCATION_REALTIME_INTERVAL = 1000; | |||||
| public static final int DEFAULT_LOCATION_REALTIME_DISTANCE = 0; // 1 | |||||
| public static final int DEFAULT_LOCATION_REALTIME_READ_COUNT = 1; | |||||
| public static final boolean DEFAULT_OPEN_LAST_URL = false; | |||||
| public static final int DEFAULT_FONT_SCALE = 100; | |||||
| public static final boolean DEFAULT_LOGCAT_CONSOLE_OUTPUT = false; | |||||
| public static final int DEFAULT_LOCATION_TIMEOUT = 0; | |||||
| public static final String FUSED_PROVIDER = "fused"; | |||||
| public static final String APK_UPDATE_DOWNLOAD_DIR = "update"; | |||||
| public static final String APK_UPDATE_DOWNLOAD_FILE = "latest.apk"; | |||||
| private NSConstants() {} | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| package com.nsgk.ruralWeb.sys; | |||||
| import android.content.Context; | |||||
| import android.content.SharedPreferences; | |||||
| import android.preference.PreferenceManager; | |||||
| import com.nsgk.ruralWeb.utils.NSStr; | |||||
| public final class NSPreference | |||||
| { | |||||
| public static final String LOCATION_MODE = "location_mode"; | |||||
| public static final String LOCATION_GAODE_INTERVAL = "location_gaode_interval"; | |||||
| public static final String LOCATION_GAODE_READ_COUNT = "location_gaode_read_count"; | |||||
| public static final String LOCATION_PROVIDER = "location_provider"; | |||||
| public static final String LOCATION_REALTIME_INTERVAL = "location_realtime_interval"; | |||||
| public static final String LOCATION_REALTIME_DISTANCE = "location_realtime_distance"; | |||||
| public static final String LOCATION_REALTIME_READ_COUNT = "location_realtime_read_count"; | |||||
| public static final String OPEN_LAST_URL = "open_last_url"; | |||||
| public static final String LAST_ACCESS_URL = "last_access_url"; | |||||
| public static final String COOKIES = "cookies"; | |||||
| public static final String FONT_SCALE = "font_scale"; | |||||
| public static final String LOGCAT_CONSOLE_OUTPUT = "logcat_console_output"; | |||||
| public static final String LOCATION_TIMEOUT = "location_timeout"; | |||||
| public static final String RESET_SETTINGS = "RESET_SETTINGS"; | |||||
| public static final String VERSION = "VERSION"; | |||||
| public static final String UPDATE_DOWNLOAD = "UPDATE_DOWNLOAD"; | |||||
| private final Context context; | |||||
| public NSPreference(Context context) | |||||
| { | |||||
| this.context = context; | |||||
| } | |||||
| public SharedPreferences Read() | |||||
| { | |||||
| return PreferenceManager.getDefaultSharedPreferences(context); | |||||
| } | |||||
| public SharedPreferences.Editor Write() | |||||
| { | |||||
| return PreferenceManager.getDefaultSharedPreferences(context).edit(); | |||||
| } | |||||
| public String GetString(String name, String... def) | |||||
| { | |||||
| return Read().getString(name, null != def && def.length > 0 ? def[0] : null); | |||||
| } | |||||
| public boolean GetBool(String name, boolean... def) | |||||
| { | |||||
| return Read().getBoolean(name, null != def && def.length > 0 ? def[0] : false); | |||||
| } | |||||
| public void Remove(String name) | |||||
| { | |||||
| Write().remove(name).commit(); | |||||
| } | |||||
| public void SetString(String name, String val) | |||||
| { | |||||
| Write().putString(name, val).commit(); | |||||
| } | |||||
| public void SetInt(String name, int val) | |||||
| { | |||||
| Write().putInt(name, val).commit(); | |||||
| } | |||||
| public int GetInt(String name, int... def) | |||||
| { | |||||
| return Read().getInt(name, null != def && def.length > 0 ? def[0] : 0); | |||||
| } | |||||
| public int GetIntFromString(String name, int defVal) | |||||
| { | |||||
| String str = Read().getString(name, ""); | |||||
| return NSStr.parseInt_s(str, defVal); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,506 @@ | |||||
| package com.nsgk.ruralWeb.ui; | |||||
| import android.app.Notification; | |||||
| import android.app.NotificationChannel; | |||||
| import android.app.NotificationManager; | |||||
| import android.content.Context; | |||||
| import android.content.DialogInterface; | |||||
| import android.content.Intent; | |||||
| import android.content.SharedPreferences; | |||||
| import android.net.Uri; | |||||
| import android.os.Build; | |||||
| import android.os.Bundle; | |||||
| import android.os.Handler; | |||||
| import android.os.HandlerThread; | |||||
| import android.os.Looper; | |||||
| import android.provider.Settings; | |||||
| import android.text.Html; | |||||
| import android.util.Log; | |||||
| import android.widget.Toast; | |||||
| import androidx.appcompat.app.AlertDialog; | |||||
| import androidx.core.app.NotificationCompat; | |||||
| import androidx.preference.Preference; | |||||
| import androidx.preference.PreferenceFragmentCompat; | |||||
| import androidx.preference.PreferenceManager; | |||||
| import androidx.preference.SeekBarPreference; | |||||
| import com.nsgk.ruralWeb.BuildConfig; | |||||
| import com.nsgk.ruralWeb.R; | |||||
| import com.nsgk.ruralWeb.enums.NSEnums; | |||||
| import com.nsgk.ruralWeb.sys.NSConstants; | |||||
| import com.nsgk.ruralWeb.sys.NSPreference; | |||||
| import com.nsgk.ruralWeb.utils.NSContextUtils; | |||||
| import com.nsgk.ruralWeb.utils.NSMisc; | |||||
| import com.nsgk.ruralWeb.utils.NSStr; | |||||
| import java.io.File; | |||||
| import java.util.Objects; | |||||
| import cn.hutool.core.io.FileUtil; | |||||
| import cn.hutool.core.io.StreamProgress; | |||||
| import cn.hutool.core.util.NumberUtil; | |||||
| import cn.hutool.core.util.StrUtil; | |||||
| import cn.hutool.http.HttpUtil; | |||||
| public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener | |||||
| { | |||||
| private static final String ID_TAG = SettingsFragment.class.getName(); | |||||
| @Override | |||||
| public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { | |||||
| addPreferencesFromResource(R.xml.settings_preference); | |||||
| Preference preference; | |||||
| final Context context = getContext(); | |||||
| preference = findPreference(NSPreference.LOCATION_MODE); | |||||
| preference.setDefaultValue(NSConstants.DEFAULT_LOCATION_MODE); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_GAODE_INTERVAL); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_GAODE_READ_COUNT); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.OPEN_LAST_URL); | |||||
| preference.setDefaultValue(NSConstants.DEFAULT_OPEN_LAST_URL); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_PROVIDER); | |||||
| preference.setDefaultValue(NSConstants.DEFAULT_LOCATION_PROVIDER); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_REALTIME_INTERVAL); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_REALTIME_DISTANCE); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_REALTIME_READ_COUNT); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.FONT_SCALE); | |||||
| preference.setDefaultValue(NSConstants.DEFAULT_FONT_SCALE); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOGCAT_CONSOLE_OUTPUT); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOGCAT_CONSOLE_OUTPUT); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.LOCATION_TIMEOUT); | |||||
| preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_TIMEOUT); | |||||
| preference.setOnPreferenceChangeListener(this); | |||||
| preference = findPreference(NSPreference.RESET_SETTINGS); | |||||
| preference.setOnPreferenceClickListener(this); | |||||
| preference = findPreference(NSPreference.VERSION); | |||||
| preference.setOnPreferenceClickListener(this); | |||||
| preference = findPreference(NSPreference.UPDATE_DOWNLOAD); | |||||
| preference.setOnPreferenceClickListener(this); | |||||
| if(StrUtil.isBlank(NSConstants.AppUpdateUrl())) | |||||
| preference.setVisible(false); | |||||
| // 总是隐藏高德的设置 | |||||
| if(!NSConstants.IsAMapLocationEnabled() || !NSContextUtils.BuildIsDebug(context)) | |||||
| { | |||||
| preference = findPreference("location_gaode"); | |||||
| preference.setVisible(false); | |||||
| } | |||||
| } | |||||
| private boolean CheckValueIsNumber(Object newValue, Integer min) | |||||
| { | |||||
| if(null != newValue) | |||||
| { | |||||
| String str = newValue.toString(); | |||||
| if(StrUtil.isNotBlank(str)) | |||||
| { | |||||
| if(!NumberUtil.isInteger(str)) | |||||
| { | |||||
| Toast.makeText(getContext(), "请输入数字", Toast.LENGTH_SHORT).show(); | |||||
| return false; | |||||
| } | |||||
| int i = Integer.parseInt(str); | |||||
| if(null != min) | |||||
| { | |||||
| if(i < min) | |||||
| { | |||||
| Toast.makeText(getContext(), "必须大于等于" + min, Toast.LENGTH_SHORT).show(); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| @Override | |||||
| public boolean onPreferenceChange(Preference preference, Object newValue) { | |||||
| String key = preference.getKey(); | |||||
| switch(key) | |||||
| { | |||||
| case NSPreference.LOCATION_GAODE_INTERVAL: | |||||
| if(!CheckValueIsNumber(newValue, 1000)) | |||||
| return false; | |||||
| break; | |||||
| case NSPreference.LOCATION_GAODE_READ_COUNT: | |||||
| if(!CheckValueIsNumber(newValue, 1)) | |||||
| return false; | |||||
| break; | |||||
| case NSPreference.LOCATION_REALTIME_INTERVAL: | |||||
| if(!CheckValueIsNumber(newValue, 1000)) | |||||
| return false; | |||||
| break; | |||||
| case NSPreference.LOCATION_REALTIME_DISTANCE: | |||||
| if(!CheckValueIsNumber(newValue, 0)) | |||||
| return false; | |||||
| break; | |||||
| case NSPreference.LOCATION_REALTIME_READ_COUNT: | |||||
| if(!CheckValueIsNumber(newValue, 1)) | |||||
| return false; | |||||
| break; | |||||
| case NSPreference.LOCATION_TIMEOUT: | |||||
| if(!CheckValueIsNumber(newValue, 0)) | |||||
| return false; | |||||
| break; | |||||
| case NSPreference.FONT_SCALE: { | |||||
| int i = (int)newValue; | |||||
| int newi = Math.round((float)i / 10.0f) * 10; | |||||
| if(i < 50) | |||||
| i = 50; | |||||
| if(i != newi) | |||||
| { | |||||
| getView().post(() -> { | |||||
| ((SeekBarPreference)preference).setValue(newi); | |||||
| preference.callChangeListener(newi); | |||||
| }); | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| } | |||||
| case NSPreference.LOCATION_MODE: | |||||
| if(!NSConstants.IsAMapLocationEnabled() && Objects.equals(NSEnums.LocationMode.GAODE, newValue)) | |||||
| { | |||||
| Log.w(ID_TAG, "高德定位被禁用"); | |||||
| return false; | |||||
| } | |||||
| if(Objects.equals(NSEnums.LocationMode.H5, newValue)) | |||||
| { | |||||
| if(!NSConstants.IsHttps()) | |||||
| { | |||||
| Log.w(ID_TAG, "非https不支持H5定位"); | |||||
| Toast.makeText(getContext(), "当前不支持H5定位", Toast.LENGTH_SHORT).show(); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| break; | |||||
| } | |||||
| SetSummary(key, newValue); | |||||
| return true; | |||||
| } | |||||
| @Override | |||||
| public boolean onPreferenceClick(final Preference preference) { | |||||
| String key = preference.getKey(); | |||||
| final Context context = getContext(); | |||||
| switch(key) | |||||
| { | |||||
| case NSPreference.RESET_SETTINGS: | |||||
| OpenQueryDialog("警告", "确定要重置到默认设置?", (dialog, which) -> { | |||||
| ResetSettings(); | |||||
| SetSummary(null, null); | |||||
| Toast.makeText(context, "设置已重置", Toast.LENGTH_LONG).show(); | |||||
| }); | |||||
| break; | |||||
| case NSPreference.VERSION: | |||||
| OpenAbout(); | |||||
| break; | |||||
| case NSPreference.UPDATE_DOWNLOAD: | |||||
| DownloadUpdate(); | |||||
| //DownloadUpdateExternally(); | |||||
| break; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private void DownloadUpdateExternally() | |||||
| { | |||||
| String updateUrl = NSConstants.AppUpdateUrl(); | |||||
| if(StrUtil.isBlank(updateUrl)) | |||||
| return; | |||||
| Context context = getContext(); | |||||
| Toast.makeText(context, "正在打开下载地址......", Toast.LENGTH_SHORT).show(); | |||||
| NSContextUtils.OpenUrlExternally(context, updateUrl); | |||||
| } | |||||
| private void DownloadUpdate() | |||||
| { | |||||
| String updateUrl = NSConstants.AppUpdateUrl(); | |||||
| if(StrUtil.isBlank(updateUrl)) | |||||
| return; | |||||
| Handler handler = new Handler(Looper.myLooper()); | |||||
| Context context = getContext(); | |||||
| Log.d(ID_TAG, "下载apk地址: " + updateUrl); | |||||
| Toast.makeText(context, "开始下载......", Toast.LENGTH_SHORT).show(); | |||||
| int icon = context.getResources().getIdentifier("ic_launcher_" + NSConstants.AppIcon()/* + "_round"*/, "mipmap", context.getApplicationContext().getPackageName()); | |||||
| NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); | |||||
| String channelId = "nsgk"; | |||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |||||
| NotificationChannel notificationChannel = notificationManager.getNotificationChannel(channelId); | |||||
| if(null == notificationChannel) | |||||
| { | |||||
| NotificationChannel channel = new NotificationChannel(channelId, "下载更新", NotificationManager.IMPORTANCE_DEFAULT); | |||||
| channel.setDescription("下载更新"); | |||||
| notificationManager.createNotificationChannel(channel); | |||||
| } | |||||
| } | |||||
| NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); | |||||
| builder.setProgress(0, 0, true) | |||||
| .setSmallIcon(icon) | |||||
| .setTicker("下载更新") | |||||
| .setOngoing(true) | |||||
| .setWhen(System.currentTimeMillis()) | |||||
| .setContentTitle("下载更新") | |||||
| .setContentText("开始下载......") | |||||
| ; | |||||
| int notifyId = 0x1003; | |||||
| Notification notification = builder.build(); | |||||
| notificationManager.notify(notifyId, notification); | |||||
| // 创建文件夹并删除缓存文件 | |||||
| File externalFilesDir = context.getExternalFilesDir(null); | |||||
| String dir = externalFilesDir.getAbsolutePath() + File.separator + NSConstants.APK_UPDATE_DOWNLOAD_DIR; | |||||
| Log.d(ID_TAG, "创建下载缓存目录: " + dir); | |||||
| notificationManager.notify(notifyId, builder.setContentText("创建下载缓存目录").build()); | |||||
| FileUtil.mkdir(dir); | |||||
| String file = dir + File.separator + NSConstants.APK_UPDATE_DOWNLOAD_FILE; | |||||
| Log.d(ID_TAG, "删除历史缓存文件: " + file); | |||||
| notificationManager.notify(notifyId, builder.setContentText("删除历史缓存文件").build()); | |||||
| FileUtil.del(file); | |||||
| new Thread(() -> { | |||||
| long bytes = HttpUtil.downloadFile(updateUrl, new File(file), new StreamProgress() { | |||||
| @Override | |||||
| public void start() | |||||
| { | |||||
| handler.post(() -> { | |||||
| notificationManager.notify(notifyId, builder.setContentText("开始下载......").setProgress(100, 0, false).build()); | |||||
| }); | |||||
| } | |||||
| @Override | |||||
| public void progress(long progressSize) | |||||
| { | |||||
| handler.post(() -> { | |||||
| notificationManager.notify(notifyId, builder.setContentText("下载: " + FileUtil.readableFileSize(progressSize)).setProgress(0, 0, true).build()); | |||||
| }); | |||||
| } | |||||
| @Override | |||||
| public void finish() | |||||
| { | |||||
| handler.post(() -> { | |||||
| notificationManager.notify(notifyId, builder.setContentText("下载完成").setProgress(100, 100, false).setOngoing(false).build()); | |||||
| }); | |||||
| } | |||||
| }); | |||||
| Log.d(ID_TAG, "下载apk文件: " + FileUtil.readableFileSize(bytes)); | |||||
| if(bytes > 0) | |||||
| { | |||||
| handler.postDelayed(() -> { | |||||
| notificationManager.notify(notifyId, builder.setContentText("准备安装更新").setProgress(0, 0, false).setOngoing(false).build()); | |||||
| Toast.makeText(context, "准备安装......", Toast.LENGTH_SHORT).show(); | |||||
| NSContextUtils.InstallApk(context, file); | |||||
| // handler.postDelayed(() -> { | |||||
| // notificationManager.cancel(notifyId); | |||||
| // }, 2000); | |||||
| }, 1000); | |||||
| } | |||||
| else | |||||
| { | |||||
| handler.postDelayed(() -> { | |||||
| notificationManager.notify(notifyId, builder.setContentText("下载更新失败").setProgress(0, 0, false).setOngoing(false).build()); | |||||
| Toast.makeText(context, "下载更新失败", Toast.LENGTH_LONG).show(); | |||||
| // handler.postDelayed(() -> { | |||||
| // notificationManager.cancel(notifyId); | |||||
| // }, 2000); | |||||
| }, 1000); | |||||
| } | |||||
| }).start(); | |||||
| } | |||||
| private void ResetSettings() | |||||
| { | |||||
| NSPreference preference = new NSPreference(getContext()); | |||||
| preference.Write() | |||||
| .putString(NSPreference.LOCATION_MODE, NSConstants.DEFAULT_LOCATION_MODE) | |||||
| .putString(NSPreference.LOCATION_GAODE_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL) | |||||
| .putString(NSPreference.LOCATION_GAODE_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT) | |||||
| .putBoolean(NSPreference.OPEN_LAST_URL, NSConstants.DEFAULT_OPEN_LAST_URL) | |||||
| .putString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER) | |||||
| .putString(NSPreference.LOCATION_REALTIME_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL) | |||||
| .putString(NSPreference.LOCATION_REALTIME_DISTANCE, "" + NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE) | |||||
| .putString(NSPreference.LOCATION_REALTIME_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT) | |||||
| .putInt(NSPreference.FONT_SCALE, NSConstants.DEFAULT_FONT_SCALE) | |||||
| .putString(NSPreference.LOCATION_TIMEOUT, "" + NSConstants.DEFAULT_LOCATION_TIMEOUT) | |||||
| .putBoolean(NSPreference.LOGCAT_CONSOLE_OUTPUT, NSConstants.DEFAULT_LOGCAT_CONSOLE_OUTPUT) | |||||
| .commit() | |||||
| ; | |||||
| } | |||||
| private void OpenQueryDialog(String title, String message, DialogInterface.OnClickListener listener) | |||||
| { | |||||
| AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); | |||||
| builder.setTitle(title); | |||||
| builder.setMessage(message); | |||||
| builder.setIcon(R.drawable.no_words); | |||||
| builder.setPositiveButton("确定", listener); | |||||
| builder.setNegativeButton("取消", null); | |||||
| AlertDialog dialog = builder.create(); | |||||
| dialog.show(); | |||||
| } | |||||
| private void OpenAbout() | |||||
| { | |||||
| Context context = getContext(); | |||||
| AlertDialog.Builder builder = new AlertDialog.Builder(context); | |||||
| String msg = "<br/>" + NSContextUtils.tr(context, R.string.company) + "<br/><br/>" + NSContextUtils.tr(context, R.string.copyright); | |||||
| msg = "<div style='text-align:center;'>" + msg + "</div>"; | |||||
| builder.setTitle(R.string.app_name2); | |||||
| builder.setMessage(Html.fromHtml(msg, Html.FROM_HTML_MODE_LEGACY, null, null)); | |||||
| int icon = context.getResources().getIdentifier("ic_launcher_" + NSConstants.AppIcon()/* + "_round"*/, "mipmap", context.getApplicationContext().getPackageName()); | |||||
| builder.setIcon(icon); | |||||
| AlertDialog dialog = builder.create(); | |||||
| dialog.show(); | |||||
| } | |||||
| @Override | |||||
| public void onResume() { | |||||
| super.onResume(); | |||||
| SetSummary(null, null); | |||||
| } | |||||
| public void SetSummary(String key, Object newValue) | |||||
| { | |||||
| Context context = getContext(); | |||||
| Preference preference; | |||||
| String summary; | |||||
| String value; | |||||
| boolean b; | |||||
| int i; | |||||
| SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); | |||||
| if(key == null || NSPreference.LOCATION_MODE.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_MODE); | |||||
| value = newValue != null ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_MODE, NSConstants.DEFAULT_LOCATION_MODE); | |||||
| summary = NSContextUtils.GetListName(context, value, R.array.location_mode_values, R.array.location_mode_labels); | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_GAODE_INTERVAL.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_GAODE_INTERVAL); | |||||
| value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_GAODE_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL); | |||||
| summary = value + "毫秒"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_GAODE_READ_COUNT.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_GAODE_READ_COUNT); | |||||
| value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_GAODE_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT); | |||||
| summary = value + "次"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.OPEN_LAST_URL.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.OPEN_LAST_URL); | |||||
| b = newValue != null ? (Boolean)newValue : sharedPreferences.getBoolean(NSPreference.OPEN_LAST_URL, NSConstants.DEFAULT_OPEN_LAST_URL); | |||||
| summary = b ? "打开最近页面" : "打开默认页面"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_PROVIDER.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_PROVIDER); | |||||
| value = newValue != null ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER); | |||||
| summary = NSContextUtils.GetListName(context, value, R.array.location_provider_values, R.array.location_provider_labels); | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_REALTIME_INTERVAL.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_REALTIME_INTERVAL); | |||||
| value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_REALTIME_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL); | |||||
| summary = value + "毫秒"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_REALTIME_DISTANCE.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_REALTIME_DISTANCE); | |||||
| value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_REALTIME_DISTANCE, "" + NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE); | |||||
| summary = value + "米"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_REALTIME_READ_COUNT.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_REALTIME_READ_COUNT); | |||||
| value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_REALTIME_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT); | |||||
| summary = value + "次"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.FONT_SCALE.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.FONT_SCALE); | |||||
| i = newValue != null ? (int)newValue : sharedPreferences.getInt(NSPreference.FONT_SCALE, NSConstants.DEFAULT_FONT_SCALE); | |||||
| if(i == 0) | |||||
| i = 100; | |||||
| summary = i + "%"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOCATION_TIMEOUT.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOCATION_TIMEOUT); | |||||
| value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_TIMEOUT, "" + NSConstants.DEFAULT_LOCATION_TIMEOUT); | |||||
| i = NSStr.parseInt_s(value, 0); | |||||
| summary = i > 0 ? i + "毫秒" : "不超时"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| if(key == null || NSPreference.LOGCAT_CONSOLE_OUTPUT.equals(key)) | |||||
| { | |||||
| preference = findPreference(NSPreference.LOGCAT_CONSOLE_OUTPUT); | |||||
| b = newValue != null ? (Boolean)newValue : sharedPreferences.getBoolean(NSPreference.LOGCAT_CONSOLE_OUTPUT, NSConstants.DEFAULT_LOGCAT_CONSOLE_OUTPUT); | |||||
| summary = b ? "控制台输出到logcat" : "不输出控制台"; | |||||
| preference.setSummary(summary); | |||||
| } | |||||
| findPreference(NSPreference.VERSION).setSummary(NSContextUtils.GetAppVersion(getContext())); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,122 @@ | |||||
| package com.nsgk.ruralWeb.utils; | |||||
| import android.app.Activity; | |||||
| import android.content.Context; | |||||
| import android.content.Intent; | |||||
| import android.content.pm.ApplicationInfo; | |||||
| import android.content.pm.PackageInfo; | |||||
| import android.content.pm.PackageManager; | |||||
| import android.content.res.Resources; | |||||
| import android.net.Uri; | |||||
| import android.os.Build; | |||||
| import android.provider.Settings; | |||||
| import android.widget.Toast; | |||||
| import androidx.core.content.FileProvider; | |||||
| import com.nsgk.ruralWeb.sys.NSConstants; | |||||
| import java.io.File; | |||||
| public final class NSContextUtils | |||||
| { | |||||
| public static void RequestLocationPermission(Activity context, int requestCode) | |||||
| { | |||||
| Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); | |||||
| context.startActivityForResult(intent, requestCode); | |||||
| } | |||||
| public static void OpenAppSetting(Context context) | |||||
| { | |||||
| Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); | |||||
| Uri uri = Uri.fromParts("package", context.getApplicationContext().getPackageName(), null); | |||||
| intent.setData(uri); | |||||
| context.startActivity(intent); | |||||
| } | |||||
| public static String GetAppVersion(Context context) | |||||
| { | |||||
| String version = "UNKNOWN"; | |||||
| try | |||||
| { | |||||
| PackageManager manager = context.getPackageManager(); | |||||
| PackageInfo info = manager.getPackageInfo(context.getApplicationContext().getPackageName(), 0); | |||||
| version = info.versionName; | |||||
| } | |||||
| catch (Exception e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return version; | |||||
| } | |||||
| public static String GetListName(Context context, String value, int keyResource, int nameResource, String... def) | |||||
| { | |||||
| Resources resources = context.getResources(); | |||||
| String[] keys = resources.getStringArray(keyResource); | |||||
| String[] names = resources.getStringArray(nameResource); | |||||
| for(int i = 0; i < keys.length; i++) | |||||
| { | |||||
| if(keys[i].equals(value)) | |||||
| return names[i]; | |||||
| } | |||||
| return null != def && def.length > 0 ? def[0] : null; | |||||
| } | |||||
| public static String tr(Context context, int id, Object...args) | |||||
| { | |||||
| return context.getResources().getString(id, args); | |||||
| } | |||||
| public static void OpenUrlExternally(Context context, String url) | |||||
| { | |||||
| Uri uri = Uri.parse(url); | |||||
| Intent intent = new Intent(Intent.ACTION_VIEW, uri); | |||||
| intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | |||||
| context.startActivity(intent); | |||||
| } | |||||
| public static void InstallApk(Context context, String apkFile) | |||||
| { | |||||
| File file = new File(apkFile); | |||||
| if(!file.isFile()) | |||||
| return; | |||||
| String packageName = context.getPackageName(); | |||||
| Uri apkUri = FileProvider.getUriForFile(context, packageName + ".fileprovider", file); | |||||
| Intent intent = new Intent(Intent.ACTION_VIEW); | |||||
| intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); | |||||
| intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | |||||
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |||||
| if (!context.getPackageManager().canRequestPackageInstalls()) { | |||||
| Toast.makeText(context, "Android 8以上安装apk请求允许未知来源App", Toast.LENGTH_LONG).show(); | |||||
| Intent settingsIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + packageName)); | |||||
| context.startActivity(settingsIntent); | |||||
| } else { | |||||
| context.startActivity(intent); | |||||
| } | |||||
| } else { | |||||
| context.startActivity(intent); | |||||
| } | |||||
| } | |||||
| public static boolean BuildIsDebug(Context context) | |||||
| { | |||||
| try | |||||
| { | |||||
| ApplicationInfo info = context.getApplicationInfo(); | |||||
| System.err.println(((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) + " -> " + NSConstants.IsDebug()); | |||||
| return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 || NSConstants.IsDebug(); | |||||
| } | |||||
| catch (Exception e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| return NSConstants.IsDebug(); // default is release | |||||
| } | |||||
| } | |||||
| private NSContextUtils() {} | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| package com.nsgk.ruralWeb.utils; | |||||
| public final class NSMisc | |||||
| { | |||||
| public static void noexcept(Runnable runnable) | |||||
| { | |||||
| if(null != runnable) | |||||
| { | |||||
| try | |||||
| { | |||||
| runnable.run(); | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package com.nsgk.ruralWeb.utils; | |||||
| import java.net.URL; | |||||
| import cn.hutool.core.util.StrUtil; | |||||
| import cn.hutool.core.util.URLUtil; | |||||
| public final class NSStr | |||||
| { | |||||
| public static String GetUrlPart(String str) | |||||
| { | |||||
| URL url = URLUtil.url(str); | |||||
| String protocol = url.getProtocol(); | |||||
| String host = url.getHost(); | |||||
| int port = url.getPort(); | |||||
| StringBuilder buf = new StringBuilder(); | |||||
| buf.append(protocol).append("://").append(host); | |||||
| if(port >= 0) | |||||
| buf.append(':').append(port); | |||||
| return buf.toString(); | |||||
| } | |||||
| public static int parseInt_s(String str, int...def) | |||||
| { | |||||
| int res = null != def && def.length > 0 ? def[0] : 0; | |||||
| if(StrUtil.isBlank(str)) | |||||
| return res; | |||||
| try | |||||
| { | |||||
| return Integer.parseInt(str); | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| return res; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,468 @@ | |||||
| package com.nsgk.ruralWeb.web; | |||||
| import android.app.Activity; | |||||
| import android.Manifest; | |||||
| import android.app.AlertDialog; | |||||
| import android.content.Context; | |||||
| import android.content.DialogInterface; | |||||
| import android.content.pm.PackageManager; | |||||
| import android.location.LocationManager; | |||||
| import android.os.Handler; | |||||
| import android.util.Log; | |||||
| import android.webkit.JavascriptInterface; | |||||
| import android.webkit.ValueCallback; | |||||
| import android.webkit.WebView; | |||||
| import android.widget.Toast; | |||||
| import androidx.core.app.ActivityCompat; | |||||
| import com.nsgk.ruralWeb.FullscreenActivity; | |||||
| import com.nsgk.ruralWeb.R; | |||||
| import com.nsgk.ruralWeb.enums.NSEnums; | |||||
| import com.nsgk.ruralWeb.location.NSLastKnownLocation; | |||||
| import com.nsgk.ruralWeb.location.NSLocationInfo; | |||||
| import com.nsgk.ruralWeb.location.NSAMapLocation; | |||||
| import com.nsgk.ruralWeb.location.NSRealtimeLocation; | |||||
| import com.nsgk.ruralWeb.location.NSSystemLocation; | |||||
| import com.nsgk.ruralWeb.sys.NSConstants; | |||||
| import com.nsgk.ruralWeb.sys.NSPreference; | |||||
| import com.nsgk.ruralWeb.utils.NSContextUtils; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Arrays; | |||||
| import java.util.List; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| public class NSEnvWindowObject | |||||
| { | |||||
| private static final String ID_TAG = NSEnvWindowObject.class.getName(); | |||||
| private final Handler m_handler; | |||||
| private final Context m_context; | |||||
| private final WebView m_webView; | |||||
| private final NSSystemLocation m_systemLocation; | |||||
| private final NSLastKnownLocation m_lastKnownLocation; | |||||
| private final NSAMapLocation m_amapLocation; | |||||
| private final NSRealtimeLocation m_realtimeLocation; | |||||
| private final NSPreference m_preference; | |||||
| // 最近一次获取到的定位坐标, 如果获取定位失败则返回此值. 可能不需要, 因为PASSIVE_PROVIDER就为最近的定位 | |||||
| private String m_lastLocation = null; | |||||
| private static final int LOCATION_STATE_READY = 0; // 准备 | |||||
| private static final int LOCATION_STATE_PROCESSING = 1; // 进行中 | |||||
| private static final int LOCATION_STATE_FINISH = 2; // 完成 | |||||
| private static final int LOCATION_STATE_TERMINATED = 3; // 中止 | |||||
| private AtomicInteger m_locationState = new AtomicInteger(LOCATION_STATE_READY); | |||||
| public NSEnvWindowObject(Context context, Handler handler, WebView webView) | |||||
| { | |||||
| m_context = context; | |||||
| m_handler = handler; | |||||
| m_webView = webView; | |||||
| m_systemLocation = new NSSystemLocation(m_context); | |||||
| m_lastKnownLocation = new NSLastKnownLocation(m_context); | |||||
| m_amapLocation = new NSAMapLocation(m_context); | |||||
| m_realtimeLocation = new NSRealtimeLocation(m_context); | |||||
| m_preference = new NSPreference(m_context); | |||||
| } | |||||
| public void OnDestroy() | |||||
| { | |||||
| if(m_amapLocation.IsInitialized()) | |||||
| m_amapLocation.Shutdown(); | |||||
| if(m_realtimeLocation.IsInitialized()) | |||||
| m_realtimeLocation.Shutdown(); | |||||
| m_systemLocation.Shutdown(); | |||||
| } | |||||
| @JavascriptInterface | |||||
| public void Toast(final String message) | |||||
| { | |||||
| ShowToast(message, Toast.LENGTH_LONG); | |||||
| } | |||||
| private void ShowToast(final String message, int duration) | |||||
| { | |||||
| RunOnUIThread(() -> { | |||||
| Toast.makeText(m_context, message, duration).show(); | |||||
| }); | |||||
| } | |||||
| @JavascriptInterface | |||||
| public void Log(final String message) | |||||
| { | |||||
| Log.i(ID_TAG, message); | |||||
| } | |||||
| @JavascriptInterface | |||||
| public String GetLocation(final String type) | |||||
| { | |||||
| Log.d(ID_TAG, "Web线程: " + Thread.currentThread().getId()); | |||||
| if(!CheckLocationPermission()) | |||||
| { | |||||
| return m_lastLocation; | |||||
| } | |||||
| SetLocationState(LOCATION_STATE_READY); | |||||
| ShowIndicator(); | |||||
| NSLocationInfo loc; | |||||
| String location = CurrentLocationMode(); | |||||
| String provider = m_preference.GetString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER); | |||||
| Log.d(ID_TAG, "定位提供器: " + provider); | |||||
| SetLocationState(LOCATION_STATE_PROCESSING); | |||||
| switch(location) | |||||
| { | |||||
| case NSEnums.LocationMode.GAODE: | |||||
| loc = GetAMapLocation(); | |||||
| break; | |||||
| case NSEnums.LocationMode.REALTIME: | |||||
| loc = GetRealtimeLocation(); | |||||
| break; | |||||
| case NSEnums.LocationMode.SYSTEM: | |||||
| default: | |||||
| loc = GetCurrentLocation(provider); | |||||
| break; | |||||
| } | |||||
| SetLocationState(LOCATION_STATE_FINISH, LOCATION_STATE_PROCESSING); | |||||
| if(null == loc) | |||||
| { | |||||
| if(IsLocationState(LOCATION_STATE_FINISH)) | |||||
| ShowToast("定位失败, 请先开启位置服务", Toast.LENGTH_LONG); | |||||
| loc = GetLastKnownLocation(provider); | |||||
| } | |||||
| HideIndicator(); | |||||
| return SetLastLocation(loc); | |||||
| } | |||||
| public String CurrentLocationMode() | |||||
| { | |||||
| return m_preference.GetString(NSPreference.LOCATION_MODE, NSConstants.DEFAULT_LOCATION_MODE); | |||||
| } | |||||
| @JavascriptInterface | |||||
| public String GetLocationMode() | |||||
| { | |||||
| return CurrentLocationMode(); | |||||
| } | |||||
| @JavascriptInterface | |||||
| public int GetLocationTimeout() | |||||
| { | |||||
| return m_preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT); | |||||
| } | |||||
| @JavascriptInterface | |||||
| public String SelectLocationMode() | |||||
| { | |||||
| final Object lock = new Object(); | |||||
| String current = CurrentLocationMode(); | |||||
| String[] res = { current }; | |||||
| synchronized(lock) { | |||||
| try | |||||
| { | |||||
| RunOnUIThread(() -> { | |||||
| AlertDialog.Builder builder = new AlertDialog.Builder(m_context); | |||||
| String[] locationModeLabels = m_context.getResources().getStringArray(R.array.location_mode_labels); | |||||
| String[] locationModeValues = m_context.getResources().getStringArray(R.array.location_mode_values); | |||||
| List<String> locationModeList = new ArrayList<>(Arrays.asList(locationModeLabels)); | |||||
| List<String> locationModeValueList = new ArrayList<>(Arrays.asList(locationModeValues)); | |||||
| int[] selected = { locationModeValueList.indexOf(current) }; | |||||
| builder.setTitle("选择定位方式") | |||||
| .setSingleChoiceItems(locationModeList.toArray(new String[0]), selected[0], (DialogInterface dialog, int which) -> { | |||||
| selected[0] = which; | |||||
| }) | |||||
| //.setCancelable(false) | |||||
| ; | |||||
| builder.setPositiveButton("确定", (DialogInterface dialog, int which) -> { | |||||
| if(selected[0] >= 0) | |||||
| { | |||||
| String mode = locationModeValueList.get(selected[0]); | |||||
| if(NSEnums.LocationMode.H5.equalsIgnoreCase(mode)) | |||||
| { | |||||
| if(!NSConstants.IsHttps()) | |||||
| { | |||||
| Toast.makeText(m_context, "当前不支持H5定位", Toast.LENGTH_SHORT).show(); | |||||
| return; | |||||
| } | |||||
| } | |||||
| m_preference.SetString(NSPreference.LOCATION_MODE, mode); | |||||
| res[0] = mode; | |||||
| String name = locationModeList.get(selected[0]); | |||||
| Toast.makeText(m_context, "使用" + name + "定位", Toast.LENGTH_SHORT).show(); | |||||
| } | |||||
| else | |||||
| Toast.makeText(m_context, "请选择定位方式", Toast.LENGTH_SHORT).show(); | |||||
| }); | |||||
| builder.setNeutralButton("默认", (DialogInterface dialog, int which) -> { | |||||
| res[0] = NSConstants.DEFAULT_LOCATION_MODE; | |||||
| m_preference.SetString(NSPreference.LOCATION_MODE, res[0]); | |||||
| int choose = locationModeValueList.indexOf(res[0]); | |||||
| String name = locationModeList.get(choose); | |||||
| Toast.makeText(m_context, "使用" + name + "定位", Toast.LENGTH_SHORT).show(); | |||||
| }); | |||||
| builder.setNegativeButton("取消", null); | |||||
| AlertDialog dialog = builder.create(); | |||||
| dialog.setOnDismissListener((DialogInterface d) -> { | |||||
| synchronized(lock) { | |||||
| lock.notifyAll(); | |||||
| } | |||||
| }); | |||||
| dialog.show(); | |||||
| }); | |||||
| lock.wait(); | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| return res[0]; | |||||
| } | |||||
| private String ReturnLocation(NSLocationInfo loc) | |||||
| { | |||||
| return loc.longitude + "," + loc.latitude; | |||||
| } | |||||
| private String SetLastLocation(NSLocationInfo loc) | |||||
| { | |||||
| if(null != loc) | |||||
| m_lastLocation = ReturnLocation(loc); | |||||
| return m_lastLocation; | |||||
| } | |||||
| private NSLocationInfo GetCurrentLocation(String type) | |||||
| { | |||||
| Log.i(ID_TAG, "使用系统当前定位"); | |||||
| NSLocationInfo loc = null; | |||||
| try | |||||
| { | |||||
| int timeout = m_preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT); | |||||
| long start = System.currentTimeMillis(); | |||||
| loc = m_systemLocation.Read(type, timeout); | |||||
| long end = System.currentTimeMillis(); | |||||
| Log.i(ID_TAG, "系统当前定位结果: " + loc + ", 耗时=" + (end - start) + "毫秒"); | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return loc; | |||||
| } | |||||
| private NSLocationInfo GetLastKnownLocation(String type) | |||||
| { | |||||
| Log.i(ID_TAG, "使用系统最近定位"); | |||||
| NSLocationInfo loc; | |||||
| try | |||||
| { | |||||
| long start = System.currentTimeMillis(); | |||||
| loc = m_lastKnownLocation.Read(type); | |||||
| long end = System.currentTimeMillis(); | |||||
| Log.i(ID_TAG, "系统定位最近结果: " + loc + ", 耗时=" + (end - start) + "毫秒"); | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return m_lastKnownLocation.GetLastLocation(); | |||||
| } | |||||
| private NSLocationInfo GetRealtimeLocation() | |||||
| { | |||||
| Log.i(ID_TAG, "使用系统实时定位"); | |||||
| NSLocationInfo loc = null; | |||||
| try | |||||
| { | |||||
| NSPreference preference = new NSPreference(m_context); | |||||
| if(!m_realtimeLocation.IsInitialized()) | |||||
| { | |||||
| m_realtimeLocation.SetOption(NSRealtimeLocation.OPTION_MIN_TIME, preference.GetIntFromString(NSPreference.LOCATION_REALTIME_INTERVAL, NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL)); | |||||
| m_realtimeLocation.SetOption(NSRealtimeLocation.OPTION_MIN_DISTANCE, preference.GetIntFromString(NSPreference.LOCATION_REALTIME_DISTANCE, NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE)); | |||||
| int flag = NSRealtimeLocation.DEFAULT_FLAG | NSRealtimeLocation.FLAG_BUILTIN_THREAD; | |||||
| String provider = preference.GetString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER); | |||||
| if(LocationManager.GPS_PROVIDER.equals(provider)) | |||||
| { | |||||
| flag &= ~NSRealtimeLocation.FLAG_PRIORITY_NETWORK; | |||||
| flag |= NSRealtimeLocation.FLAG_PRIORITY_GPS; | |||||
| } | |||||
| m_realtimeLocation.Init(flag); | |||||
| } | |||||
| long start = System.currentTimeMillis(); | |||||
| int count = preference.GetIntFromString(NSPreference.LOCATION_REALTIME_READ_COUNT, NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT); | |||||
| int timeout = preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT); | |||||
| loc = m_realtimeLocation.Read(count, timeout); | |||||
| long end = System.currentTimeMillis(); | |||||
| Log.i(ID_TAG, "系统实时定位结果: " + loc + ", 耗时=" + (end - start) + "毫秒"); | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return loc; | |||||
| } | |||||
| public NSLocationInfo GetAMapLocation() | |||||
| { | |||||
| Log.i(ID_TAG, "使用高德定位"); | |||||
| NSLocationInfo loc = null; | |||||
| try | |||||
| { | |||||
| NSPreference preference = new NSPreference(m_context); | |||||
| if(!m_amapLocation.IsInitialized()) | |||||
| { | |||||
| m_amapLocation.SetOption(NSAMapLocation.OPTION_INTERVAL, preference.GetIntFromString(NSPreference.LOCATION_GAODE_INTERVAL, NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL)); | |||||
| m_amapLocation.Init(NSAMapLocation.DEFAULT_FLAG | NSAMapLocation.FLAG_BUILTIN_THREAD); | |||||
| } | |||||
| long start = System.currentTimeMillis(); | |||||
| int count = preference.GetIntFromString(NSPreference.LOCATION_GAODE_READ_COUNT, NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT); | |||||
| int timeout = preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT); | |||||
| loc = m_amapLocation.Read(count, timeout); | |||||
| long end = System.currentTimeMillis(); | |||||
| Log.i(ID_TAG, "高德定位结果: " + loc + ", 耗时=" + (end - start) + "毫秒"); | |||||
| } | |||||
| catch(Throwable e) | |||||
| { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| return loc; | |||||
| } | |||||
| private boolean CheckLocationPermission() | |||||
| { | |||||
| if(ActivityCompat.checkSelfPermission(m_context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(m_context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) | |||||
| { | |||||
| Activity activity = (Activity) m_context; | |||||
| if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) && activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)) // do not ask | |||||
| { | |||||
| Toast.makeText(m_context, "请先允许定位服务", Toast.LENGTH_LONG).show(); | |||||
| NSContextUtils.RequestLocationPermission(activity, FullscreenActivity.PERMISSION_LOCATION_REQUEST_CODE); | |||||
| } | |||||
| else | |||||
| { | |||||
| activity.requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION }, FullscreenActivity.PERMISSION_LOCATION_REQUEST_CODE); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| else | |||||
| return true; | |||||
| } | |||||
| protected void RunOnUIThread(Runnable runnable) | |||||
| { | |||||
| if(null != m_handler) | |||||
| m_handler.post(runnable); | |||||
| else if(m_context instanceof Activity) | |||||
| ((Activity)m_context).runOnUiThread(runnable); | |||||
| else | |||||
| { | |||||
| Log.e(ID_TAG, "无法在UI线程中执行"); | |||||
| } | |||||
| } | |||||
| protected void CallJSFunc(final String func, Object...args) | |||||
| { | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| for(int i = 0; i < args.length; i++) | |||||
| { | |||||
| sb.append("'").append(args[i].toString()).append("'"); | |||||
| if(i < args.length - 1) | |||||
| sb.append(", "); | |||||
| } | |||||
| final String arg = sb.toString(); | |||||
| RunOnUIThread(() -> { | |||||
| String script = "javascript:typeof(" + func + ") == 'function' && " + func + "(" + arg + ");"; | |||||
| Log.e(ID_TAG, String.format("调用js函数: 函数(%s), 参数(%s)", func, arg)); | |||||
| m_webView.evaluateJavascript(script, new ValueCallback<String>() { | |||||
| @Override | |||||
| public void onReceiveValue(String value) { | |||||
| Log.e(ID_TAG, String.format("js函数 %s 返回: %s", func, value)); | |||||
| } | |||||
| }); | |||||
| }); | |||||
| } | |||||
| protected void CallJSFunc_beforeAndroid4_4(final String func, Object...args) | |||||
| { | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| for(int i = 0; i < args.length; i++) | |||||
| { | |||||
| sb.append("'").append(args[i].toString()).append("'"); | |||||
| if(i < args.length - 1) | |||||
| sb.append(", "); | |||||
| } | |||||
| final String arg = sb.toString(); | |||||
| RunOnUIThread(() -> { | |||||
| Log.e(ID_TAG, String.format("调用js函数: 函数(%s), 参数(%s)", func, arg)); | |||||
| String script = "javascript:typeof(" + func + ") == 'function' && " + func + "(" + arg + ");"; | |||||
| Log.e(ID_TAG, script); | |||||
| m_webView.loadUrl(script, null); | |||||
| }); | |||||
| } | |||||
| protected void ShowIndicator() | |||||
| { | |||||
| RunOnUIThread(() -> { | |||||
| ((FullscreenActivity)(m_context)).SetIndicatorVisible(true); | |||||
| }); | |||||
| } | |||||
| protected void HideIndicator() | |||||
| { | |||||
| RunOnUIThread(() -> { | |||||
| ((FullscreenActivity)(m_context)).SetIndicatorVisible(false); | |||||
| }); | |||||
| } | |||||
| public void TerminateLocation() | |||||
| { | |||||
| if(!IsLocationState(LOCATION_STATE_PROCESSING)) | |||||
| return; | |||||
| SetLocationState(LOCATION_STATE_TERMINATED, LOCATION_STATE_PROCESSING); | |||||
| String location = CurrentLocationMode(); | |||||
| Log.i(ID_TAG, "中止定位: " + location); | |||||
| switch(location) | |||||
| { | |||||
| case NSEnums.LocationMode.GAODE: | |||||
| m_amapLocation.TerminateRead(); | |||||
| break; | |||||
| case NSEnums.LocationMode.REALTIME: | |||||
| m_realtimeLocation.TerminateRead(); | |||||
| break; | |||||
| case NSEnums.LocationMode.SYSTEM: | |||||
| m_systemLocation.TerminateRead(); | |||||
| break; | |||||
| } | |||||
| HideIndicator(); | |||||
| } | |||||
| private void SetLocationState(int st) | |||||
| { | |||||
| m_locationState.set(st); | |||||
| } | |||||
| private void SetLocationState(int st, int cur) | |||||
| { | |||||
| m_locationState.compareAndSet(cur, st); | |||||
| } | |||||
| private boolean IsLocationState(int st) | |||||
| { | |||||
| return m_locationState.get() == st; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" > | |||||
| <solid android:color="@color/black_overlay" /> | |||||
| <padding | |||||
| android:bottom="10dp" | |||||
| android:left="20dp" | |||||
| android:right="20dp" | |||||
| android:top="10dp" /> | |||||
| <corners android:radius="10dp" /> | |||||
| </shape> | |||||
| @@ -32,19 +32,21 @@ | |||||
| android:layout_marginBottom="8dp"> | android:layout_marginBottom="8dp"> | ||||
| <TextView | <TextView | ||||
| android:id="@+id/welcome_text_copyright" | |||||
| android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
| android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
| android:layout_gravity="center" | android:layout_gravity="center" | ||||
| android:gravity="center" | android:gravity="center" | ||||
| android:text="Copyright © 2024 ZNRX All Rights Reserved." /> | |||||
| android:text="@string/copyright" /> | |||||
| <TextView | <TextView | ||||
| android:id="@+id/welcome_text_vendor" | |||||
| android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
| android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
| android:layout_gravity="center" | android:layout_gravity="center" | ||||
| android:gravity="center" | android:gravity="center" | ||||
| android:textStyle="bold" | android:textStyle="bold" | ||||
| android:text="中农融信(北京)科技股份有限公司" /> | |||||
| android:text="@string/company" /> | |||||
| </LinearLayout> | </LinearLayout> | ||||
| @@ -0,0 +1,24 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| android:orientation="vertical" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| android:background="@drawable/indicator_background"> | |||||
| <ProgressBar | |||||
| android:id="@+id/progress_indicator" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| android:layout_gravity="center_horizontal" | |||||
| android:indeterminate="true" | |||||
| /> | |||||
| <Button | |||||
| android:id="@+id/terminate_button" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| android:layout_gravity="center_horizontal" | |||||
| android:layout_marginTop="2dp" | |||||
| android:text="停止" /> | |||||
| </LinearLayout> | |||||
| @@ -0,0 +1,28 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <resources> | |||||
| <string-array name="location_mode_labels"> | |||||
| <item>系统(实时)</item> | |||||
| <item>系统</item> | |||||
| <item>H5</item> | |||||
| <item>高德</item> | |||||
| </string-array> | |||||
| <string-array name="location_mode_values"> | |||||
| <item>realtime</item> | |||||
| <item>system</item> | |||||
| <item>h5</item> | |||||
| <item>gaode</item> | |||||
| </string-array> | |||||
| <string-array name="location_provider_labels"> | |||||
| <item>卫星</item> | |||||
| <item>网络</item> | |||||
| </string-array> | |||||
| <string-array name="location_provider_values"> | |||||
| <item>gps</item> | |||||
| <item>network</item> | |||||
| </string-array> | |||||
| </resources> | |||||
| @@ -3,4 +3,9 @@ | |||||
| <string name="app_name">宅基地</string> | <string name="app_name">宅基地</string> | ||||
| <string name="dummy_button">Dummy Button</string> | <string name="dummy_button">Dummy Button</string> | ||||
| <string name="dummy_content">DUMMY\nCONTENT</string> | <string name="dummy_content">DUMMY\nCONTENT</string> | ||||
| <string name="copyright">Copyright © 2024 ZNRX All Rights Reserved.</string> | |||||
| <string name="company">中农融信(北京)科技股份有限公司</string> | |||||
| <string name="settings_name">设置</string> | |||||
| </resources> | </resources> | ||||
| @@ -0,0 +1,7 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <paths> | |||||
| <external-path path="Android/data/com.nsgk.ruralWeb" name="external_root"/> | |||||
| <external-path path="nsgk/ruralWeb" name="ruralweb_root"/> | |||||
| <external-path path="nsgk" name="nsgk_root"/> | |||||
| <external-path path="." name="external_storage_root"/> | |||||
| </paths> | |||||
| @@ -0,0 +1,149 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||||
| android:title="设置"> | |||||
| <PreferenceCategory | |||||
| android:key="base" | |||||
| android:title="基础" | |||||
| > | |||||
| <SwitchPreference | |||||
| android:title="进入App打开最近的页面" | |||||
| android:key="open_last_url" | |||||
| android:persistent="true" | |||||
| android:defaultValue="false" | |||||
| android:summary="打开最近的页面" | |||||
| /> | |||||
| <SeekBarPreference | |||||
| android:title="字体缩放倍数(%)" | |||||
| android:key="font_scale" | |||||
| android:persistent="true" | |||||
| android:defaultValue="100" | |||||
| app:min="50" | |||||
| android:max="300" | |||||
| app:showSeekBarValue="true" | |||||
| android:summary="字体缩放倍数" | |||||
| /> | |||||
| <ListPreference | |||||
| android:dialogTitle="定位方式" | |||||
| android:key="location_mode" | |||||
| android:entries="@array/location_mode_labels" | |||||
| android:entryValues="@array/location_mode_values" | |||||
| android:summary="定位方式" | |||||
| android:persistent="true" | |||||
| android:title="选择定位方式" | |||||
| android:defaultValue="realtime" | |||||
| /> | |||||
| <EditTextPreference | |||||
| android:dialogTitle="定位超时(毫秒) 0为不超时" | |||||
| android:key="location_timeout" | |||||
| android:summary="定位超时" | |||||
| android:persistent="true" | |||||
| android:title="设置定位超时" | |||||
| android:inputType="number" | |||||
| android:defaultValue="0" | |||||
| /> | |||||
| </PreferenceCategory> | |||||
| <PreferenceCategory | |||||
| android:key="location_gaode" | |||||
| android:title="高德定位" | |||||
| > | |||||
| <EditTextPreference | |||||
| android:dialogTitle="定位间隔(毫秒)" | |||||
| android:key="location_gaode_interval" | |||||
| android:summary="定位间隔" | |||||
| android:persistent="true" | |||||
| android:title="设置定位间隔" | |||||
| android:inputType="number" | |||||
| android:defaultValue="1000" | |||||
| /> | |||||
| <EditTextPreference | |||||
| android:dialogTitle="每次定位次数" | |||||
| android:key="location_gaode_read_count" | |||||
| android:summary="定位次数" | |||||
| android:persistent="true" | |||||
| android:title="设置定位次数" | |||||
| android:inputType="number" | |||||
| android:defaultValue="1" | |||||
| /> | |||||
| </PreferenceCategory> | |||||
| <PreferenceCategory | |||||
| android:key="location_system" | |||||
| android:title="系统定位" | |||||
| > | |||||
| <ListPreference | |||||
| android:dialogTitle="定位提供器" | |||||
| android:key="location_provider" | |||||
| android:entries="@array/location_provider_labels" | |||||
| android:entryValues="@array/location_provider_values" | |||||
| android:summary="定位提供器" | |||||
| android:persistent="true" | |||||
| android:title="选择定位提供器" | |||||
| android:defaultValue="gps" | |||||
| /> | |||||
| <EditTextPreference | |||||
| android:dialogTitle="定位间隔(毫秒)" | |||||
| android:key="location_realtime_interval" | |||||
| android:summary="定位间隔" | |||||
| android:persistent="true" | |||||
| android:title="设置定位间隔" | |||||
| android:inputType="number" | |||||
| android:defaultValue="1000" | |||||
| /> | |||||
| <EditTextPreference | |||||
| android:dialogTitle="定位距离(米)" | |||||
| android:key="location_realtime_distance" | |||||
| android:summary="定位距离" | |||||
| android:persistent="true" | |||||
| android:title="设置定位距离" | |||||
| android:inputType="number" | |||||
| android:defaultValue="0" | |||||
| /> | |||||
| <EditTextPreference | |||||
| android:dialogTitle="每次定位次数" | |||||
| android:key="location_realtime_read_count" | |||||
| android:summary="定位次数" | |||||
| android:persistent="true" | |||||
| android:title="设置定位次数" | |||||
| android:inputType="number" | |||||
| android:defaultValue="1" | |||||
| /> | |||||
| </PreferenceCategory> | |||||
| <PreferenceCategory | |||||
| android:key="other" | |||||
| android:title="其他" | |||||
| > | |||||
| <SwitchPreference | |||||
| android:title="控制台输出到logcat" | |||||
| android:key="logcat_console_output" | |||||
| android:persistent="true" | |||||
| android:defaultValue="false" | |||||
| android:summary="控制台输出到logcat" | |||||
| /> | |||||
| <Preference | |||||
| android:key="RESET_SETTINGS" | |||||
| android:summary="重置所有设置为默认值" | |||||
| android:title="重置设置" | |||||
| android:persistent="false"> | |||||
| </Preference> | |||||
| <Preference | |||||
| android:key="UPDATE_DOWNLOAD" | |||||
| android:summary="下载更新" | |||||
| android:title="下载更新" | |||||
| android:persistent="false"> | |||||
| </Preference> | |||||
| <Preference | |||||
| android:key="VERSION" | |||||
| android:title="版本" | |||||
| android:summary="" | |||||
| android:persistent="false"> | |||||
| </Preference> | |||||
| </PreferenceCategory> | |||||
| </PreferenceScreen> | |||||
| @@ -0,0 +1,17 @@ | |||||
| <shortcuts xmlns:tools="http://schemas.android.com/tools" | |||||
| xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
| <shortcut | |||||
| android:shortcutId="shortcut_settings" | |||||
| android:enabled="true" | |||||
| android:icon="@drawable/no_words" | |||||
| android:shortcutShortLabel="@string/settings_name" | |||||
| tools:targetApi="n_mr1"> | |||||
| <intent | |||||
| android:action="android.intent.action.VIEW" | |||||
| android:targetPackage="com.nsgk.ruralWeb" | |||||
| android:targetClass="com.nsgk.ruralWeb.SettingsActivity" | |||||
| android:name="android.shortcut.conversation"/> | |||||
| </shortcut> | |||||
| </shortcuts> | |||||
| @@ -14,4 +14,27 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 | |||||
| # AndroidX package structure to make it clearer which packages are bundled with the | # 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 | # Android operating system, and which are packaged with your app"s APK | ||||
| # https://developer.android.com/topic/libraries/support-library/androidx-rn | # https://developer.android.com/topic/libraries/support-library/androidx-rn | ||||
| android.useAndroidX=true | |||||
| android.useAndroidX=true | |||||
| # Signing | |||||
| storeFile=../app-keystore.jks | |||||
| storePassword=ns61GK32x% | |||||
| keyAlias=nsgk_rural_web | |||||
| keyPassword=ns61GK32x% | |||||
| # Command line arguments | |||||
| # [String] App home url, it will save to @string/app_home_url | |||||
| appHomeUrl=http://218.59.175.43:71/yinnongLogin | |||||
| # [String] App custom name, it will be set on res/values/strings.xml | |||||
| appName=@string/app_name | |||||
| # [String] App icon mipmap key name in res/mipmaps, icon = ic_launcher_${appIconKey}, round icon = ic_launcher_${appIconKey}_round | |||||
| appIconKey=yhzl | |||||
| # [String] App copyright name, it will save to @string/app_copyright. default using @string/copyright | |||||
| appCopyright= | |||||
| # [String] App vendor name, it will save to @string/app_vendor. default using @string/company | |||||
| appVendor= | |||||
| # [String] App update download url, it will save to @string/app_update_url | |||||
| # http://218.59.175.43:8090/nsgk/qixingguan.apk | |||||
| appUpdateUrl= | |||||
| # gaode amap key | |||||
| amapKey=490bef43ef0182379aa1a8bacc45d054 | |||||
| @@ -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" "$@" | |||||
| @@ -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 | |||||
| @@ -0,0 +1,7 @@ | |||||
| @echo off | |||||
| echo 갛陋apk(랙꼈경) | |||||
| call adb install app\build\outputs\apk\release\app-release.apk | |||||
| pause; | |||||
| @@ -0,0 +1,7 @@ | |||||
| @echo off | |||||
| echo °²×°apk(Debug) | |||||
| call adb install app\build\outputs\apk\debug\app-debug.apk | |||||
| pause; | |||||
| @@ -0,0 +1,10 @@ | |||||
| @echo off | |||||
| echo 뭐쉔apk(딧桿관) | |||||
| call gradlew assembleDebug | |||||
| echo 댔역app-debug.apk杰瞳커쩌...... | |||||
| start "" app\build\outputs\apk\debug | |||||
| pause; | |||||
| @@ -0,0 +1,48 @@ | |||||
| @echo off | |||||
| echo 构建apk(正式) | |||||
| echo 用法: %0 App主页链接地址 App名称 App图标 "App版权信息" App厂商信息 | |||||
| echo 任意参数不传则使用App项目里gradle.properties的默认配置 | |||||
| set NUM_ARG=0 | |||||
| for %%x in (%*) do ( | |||||
| set /a NUM_ARG+=1 | |||||
| ) | |||||
| echo 参数数量: %NUM_ARG% | |||||
| if %NUM_ARG% GEQ 5 ( | |||||
| echo App主页链接地址: %1 | |||||
| echo App名称: %2 | |||||
| echo App图标: ic_launcher_%3; 圆角图标: ic_launcher_%3_round | |||||
| echo App版权信息: %4 | |||||
| echo App厂商信息: %5 | |||||
| call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 -PappIconKey=%3 -PappCopyright=%4 -PappVendor=%5 | |||||
| ) else if %NUM_ARG% GEQ 4 ( | |||||
| echo App主页链接地址: %1 | |||||
| echo App名称: %2 | |||||
| echo App图标: ic_launcher_%3 圆角图标: ic_launcher_%3_round | |||||
| echo App版权信息: %4 | |||||
| call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 -PappIconKey=%3 -PappCopyright=%4 | |||||
| ) else if %NUM_ARG% GEQ 3 ( | |||||
| echo App主页链接地址: %1 | |||||
| echo App名称: %2 | |||||
| echo App图标: ic_launcher_%3; 圆角图标: ic_launcher_%3_round | |||||
| call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 -PappIconKey=%3 | |||||
| ) else if %NUM_ARG% GEQ 2 ( | |||||
| echo App主页链接地址: %1 | |||||
| echo App名称: %2 | |||||
| call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 | |||||
| ) else if %NUM_ARG% GEQ 1 ( | |||||
| echo App主页链接地址: %1 | |||||
| call gradlew assembleRelease -PappHomeUrl=%1 | |||||
| ) else ( | |||||
| echo 使用gradle.properties默认配置 | |||||
| call gradlew assembleRelease | |||||
| ) | |||||
| echo 打开app-release.apk所在目录...... | |||||
| start "" app\build\outputs\apk\release | |||||
| pause; | |||||
| @@ -0,0 +1,15 @@ | |||||
| @echo off | |||||
| echo 生成keystore | |||||
| call keytool -genkeypair -v -keystore app-keystore.jks -alias nsgk_rural_web -keyalg RSA -keysize 2048 -validity 365000 | |||||
| rem 密钥库口令: ns61GK32x% | |||||
| rem 您的名字与姓氏是什么? ZZL | |||||
| rem 您的组织单位名称是什么? NSGK | |||||
| rem 您的组织名称是什么? NSGK | |||||
| rem 您所在的城市或区域名称是什么? | |||||
| rem 您所在的省/市/自治区名称是什么? | |||||
| rem 该单位的双字母国家/地区代码是什么? CN | |||||
| pause; | |||||