diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc103f1 --- /dev/null +++ b/README.md @@ -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 +``` \ No newline at end of file diff --git a/app-keystore.jks b/app-keystore.jks new file mode 100644 index 0000000..132bf4f Binary files /dev/null and b/app-keystore.jks differ diff --git a/app/build.gradle b/app/build.gradle index c5972a8..7c33dd3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,15 +10,66 @@ android { minSdkVersion 24 targetSdkVersion 30 versionCode 1 - versionName "1.0" + versionName "1.0.0.1" 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 { release { minifyEnabled false 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 { @@ -39,4 +90,9 @@ dependencies { 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-filechooser:v4.1.9-androidx' -} \ No newline at end of file + 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' +} diff --git a/app/libs/AMap_Location_V6.4.9_20241226.jar b/app/libs/AMap_Location_V6.4.9_20241226.jar new file mode 100644 index 0000000..648692e Binary files /dev/null and b/app/libs/AMap_Location_V6.4.9_20241226.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7961336..b5e001d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,33 +13,63 @@ - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java b/app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java index d776d22..ad23702 100644 --- a/app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java +++ b/app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java @@ -1,28 +1,62 @@ package com.nsgk.ruralWeb; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.util.Log; import android.view.KeyEvent; +import android.view.View; 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.ProgressBar; import android.widget.RelativeLayout; +import android.widget.Toast; import com.just.agentweb.AgentWeb; +import com.just.agentweb.AgentWebConfig; 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.appcompat.app.AppCompatActivity; +import cn.hutool.core.util.StrUtil; + /** * An example full-screen activity that shows and hides the system UI (i.e. * status bar and navigation/system bar) with user interaction. */ 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 NSPreference preference; + private NSEnvWindowObject envWindowObject; + + private String m_lastUrl; + private String m_mainUrl; + private View m_progressIndicator; @SuppressLint("SetJavaScriptEnabled") @Override @@ -30,12 +64,71 @@ public class FullscreenActivity extends AppCompatActivity { super.onCreate(savedInstanceState); 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(); - 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。 - .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,同时相应的中间件也会失效。 .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 加入。 @@ -43,7 +136,51 @@ public class FullscreenActivity extends AppCompatActivity { .interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 .createAgentWeb()//创建AgentWeb。 .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项目的入口地址 事项审批 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 */ + Log.d(ID_TAG, "UI线程: " + Thread.currentThread().getId()); } @Override protected void onPause() { + if(IsRecordUrl()) + { + DumpCookie(); + } mAgentWeb.getWebLifeCycle().onPause(); super.onPause(); } @@ -75,25 +217,165 @@ public class FullscreenActivity extends AppCompatActivity { @Override 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(); + envWindowObject.OnDestroy(); super.onDestroy(); } @Override 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)) { return true; } return super.onKeyDown(keyCode, event); } + @Override + public void onBackPressed() + { + if(!HandleBackKeyEvent(2)) + super.onBackPressed(); + } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent 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; + } } \ No newline at end of file diff --git a/app/src/main/java/com/nsgk/ruralWeb/SettingsActivity.java b/app/src/main/java/com/nsgk/ruralWeb/SettingsActivity.java new file mode 100644 index 0000000..af7d01f --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/SettingsActivity.java @@ -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(); + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/WelcomeActivity.java b/app/src/main/java/com/nsgk/ruralWeb/WelcomeActivity.java index de561e9..f68d558 100644 --- a/app/src/main/java/com/nsgk/ruralWeb/WelcomeActivity.java +++ b/app/src/main/java/com/nsgk/ruralWeb/WelcomeActivity.java @@ -9,10 +9,13 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import com.nsgk.ruralWeb.sys.NSConstants; + public class WelcomeActivity extends AppCompatActivity { private ImageView imageView; 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 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_welcome); imageView = findViewById(R.id.imageView); 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.start(); handler.sendEmptyMessageDelayed(1, 1000); diff --git a/app/src/main/java/com/nsgk/ruralWeb/enums/NSEnums.java b/app/src/main/java/com/nsgk/ruralWeb/enums/NSEnums.java new file mode 100644 index 0000000..cb9a5e2 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/enums/NSEnums.java @@ -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() {} +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java b/app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java new file mode 100644 index 0000000..7af0885 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java @@ -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 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(); + /** 设置包含隐私政策,并展示用户授权弹窗 必须在AmapLocationClient实例化之前调用 + * + * @param context + * @param isContains: 是隐私权政策是否包含高德开平隐私权政策 true是包含 + * @param isShow: 隐私权政策是否弹窗展示告知用户 true是展示 + * @since 5.6.0 + */ + AMapLocationClient.updatePrivacyShow(applicationContext, true, true); + /** + * 设置是否同意用户授权政策 必须在AmapLocationClient实例化之前调用 + * @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 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; + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/location/NSLastKnownLocation.java b/app/src/main/java/com/nsgk/ruralWeb/location/NSLastKnownLocation.java new file mode 100644 index 0000000..a1e35c7 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/location/NSLastKnownLocation.java @@ -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 allProviders = lm.getAllProviders(); + Log.i(ID_TAG, "使用最近所有定位提供器: " + allProviders); + Map 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nsgk/ruralWeb/location/NSLocationInfo.java b/app/src/main/java/com/nsgk/ruralWeb/location/NSLocationInfo.java new file mode 100644 index 0000000..6d7266a --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/location/NSLocationInfo.java @@ -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 + ; + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/location/NSRealtimeLocation.java b/app/src/main/java/com/nsgk/ruralWeb/location/NSRealtimeLocation.java new file mode 100644 index 0000000..9ce1c01 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/location/NSRealtimeLocation.java @@ -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 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 GetOption(int type, T def) + { + Object o = m_options.get(type); + if(null == o) + return def; + return (T) o; + } + + private void InitOptions() + { + List 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(); + } + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/location/NSSystemLocation.java b/app/src/main/java/com/nsgk/ruralWeb/location/NSSystemLocation.java new file mode 100644 index 0000000..8df913f --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/location/NSSystemLocation.java @@ -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 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 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nsgk/ruralWeb/sys/NSConstants.java b/app/src/main/java/com/nsgk/ruralWeb/sys/NSConstants.java new file mode 100644 index 0000000..6e01d87 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/sys/NSConstants.java @@ -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() {} +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/sys/NSPreference.java b/app/src/main/java/com/nsgk/ruralWeb/sys/NSPreference.java new file mode 100644 index 0000000..2eb3b0f --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/sys/NSPreference.java @@ -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); + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/ui/SettingsFragment.java b/app/src/main/java/com/nsgk/ruralWeb/ui/SettingsFragment.java new file mode 100644 index 0000000..ad42f14 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/ui/SettingsFragment.java @@ -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 = "
" + NSContextUtils.tr(context, R.string.company) + "

" + NSContextUtils.tr(context, R.string.copyright); + msg = "
" + msg + "
"; + 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())); + } +} + diff --git a/app/src/main/java/com/nsgk/ruralWeb/utils/NSContextUtils.java b/app/src/main/java/com/nsgk/ruralWeb/utils/NSContextUtils.java new file mode 100644 index 0000000..de1c308 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/utils/NSContextUtils.java @@ -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() {} +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/utils/NSMisc.java b/app/src/main/java/com/nsgk/ruralWeb/utils/NSMisc.java new file mode 100644 index 0000000..dfec389 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/utils/NSMisc.java @@ -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(); + } + } + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/utils/NSStr.java b/app/src/main/java/com/nsgk/ruralWeb/utils/NSStr.java new file mode 100644 index 0000000..b902911 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/utils/NSStr.java @@ -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; + } + } +} diff --git a/app/src/main/java/com/nsgk/ruralWeb/web/NSEnvWindowObject.java b/app/src/main/java/com/nsgk/ruralWeb/web/NSEnvWindowObject.java new file mode 100644 index 0000000..a5b214e --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/web/NSEnvWindowObject.java @@ -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 locationModeList = new ArrayList<>(Arrays.asList(locationModeLabels)); + List 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() { + @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; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/indicator_background.xml b/app/src/main/res/drawable/indicator_background.xml new file mode 100644 index 0000000..825c101 --- /dev/null +++ b/app/src/main/res/drawable/indicator_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml index 88e7cac..cd07a83 100644 --- a/app/src/main/res/layout/activity_welcome.xml +++ b/app/src/main/res/layout/activity_welcome.xml @@ -32,19 +32,21 @@ android:layout_marginBottom="8dp"> + android:text="@string/copyright" /> + android:text="@string/company" /> diff --git a/app/src/main/res/layout/location_indicator.xml b/app/src/main/res/layout/location_indicator.xml new file mode 100644 index 0000000..2628d40 --- /dev/null +++ b/app/src/main/res/layout/location_indicator.xml @@ -0,0 +1,24 @@ + + + + + +