From 80cc1248b22014225bf25a53dbfa9d474d81c31e Mon Sep 17 00:00:00 2001 From: wangcongtao Date: Mon, 23 Dec 2019 15:08:04 +0800 Subject: [PATCH] Initial commit --- .gitignore | 14 + .idea/codeStyles/Project.xml | 116 +++ .idea/gradle.xml | 16 + .idea/misc.xml | 14 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 29 + app/proguard-rules.pro | 21 + .../launcher/ExampleInstrumentedTest.java | 27 + app/src/main/AndroidManifest.xml | 16 + .../com/mogo/launcher/MogoApplication.java | 17 + .../drawable-v24/ic_launcher_foreground.xml | 34 + .../res/drawable/ic_launcher_background.xml | 170 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 37 + .../com/mogo/launcher/ExampleUnitTest.java | 17 + build.gradle | 45 + config.gradle | 53 + foudations/mogo-commons/.gitignore | 1 + foudations/mogo-commons/build.gradle | 31 + foudations/mogo-commons/consumer-rules.pro | 0 foudations/mogo-commons/proguard-rules.pro | 21 + .../mogo-commons/src/main/AndroidManifest.xml | 2 + .../com/mogo/commons/AbsMogoApplication.java | 24 + .../java/com/mogo/commons/data/BaseData.java | 12 + .../network/AllAllowedHostnameVerifier.java | 18 + .../commons/network/X509TrustManagerImpl.java | 30 + .../java/com/mogo/commons/voice/AIAssist.java | 102 ++ .../commons/voice/IMogoVoiceCmdCallBack.java | 38 + .../src/main/res/values/colors.xml | 2 + .../src/main/res/values/strings.xml | 3 + foudations/mogo-utils/.gitignore | 1 + foudations/mogo-utils/build.gradle | 40 + foudations/mogo-utils/consumer-rules.pro | 0 foudations/mogo-utils/proguard-rules.pro | 21 + .../mogo/utils/ExampleInstrumentedTest.java | 27 + .../mogo-utils/src/main/AndroidManifest.xml | 2 + .../mogo/utils/ActivityLifecycleManager.java | 335 ++++++ .../java/com/mogo/utils/ActivityStack.java | 84 ++ .../main/java/com/mogo/utils/ArrayUtils.java | 464 +++++++++ .../java/com/mogo/utils/BitmapHelper.java | 673 ++++++++++++ .../main/java/com/mogo/utils/CheckUtils.java | 199 ++++ .../main/java/com/mogo/utils/CommonUtils.java | 573 +++++++++++ .../java/com/mogo/utils/CountDownTimer.java | 151 +++ .../java/com/mogo/utils/DateTimeUtils.java | 452 ++++++++ .../java/com/mogo/utils/DeviceIdUtils.java | 118 +++ .../main/java/com/mogo/utils/FileUtils.java | 250 +++++ .../src/main/java/com/mogo/utils/IOUtils.java | 49 + .../main/java/com/mogo/utils/IntentUtils.java | 361 +++++++ .../main/java/com/mogo/utils/MapUtils.java | 25 + .../java/com/mogo/utils/MatcherUtils.java | 398 +++++++ .../java/com/mogo/utils/ProcessUtils.java | 86 ++ .../java/com/mogo/utils/ReflectUtils.java | 73 ++ .../java/com/mogo/utils/ResourcesHelper.java | 72 ++ .../com/mogo/utils/SoftKeyboardUtils.java | 33 + .../com/mogo/utils/ThreadPoolService.java | 16 + .../main/java/com/mogo/utils/TipToast.java | 176 ++++ .../java/com/mogo/utils/UiThreadHandler.java | 41 + .../java/com/mogo/utils/ValidateUtils.java | 10 + .../main/java/com/mogo/utils/WindowUtils.java | 71 ++ .../com/mogo/utils/digest/BinaryDecoder.java | 38 + .../com/mogo/utils/digest/BinaryEncoder.java | 38 + .../com/mogo/utils/digest/CharEncoding.java | 113 ++ .../mogo/utils/digest/CharSequenceUtils.java | 79 ++ .../java/com/mogo/utils/digest/Charsets.java | 156 +++ .../java/com/mogo/utils/digest/Decoder.java | 47 + .../mogo/utils/digest/DecoderException.java | 86 ++ .../com/mogo/utils/digest/DigestUtils.java | 817 +++++++++++++++ .../java/com/mogo/utils/digest/Encoder.java | 44 + .../mogo/utils/digest/EncoderException.java | 89 ++ .../main/java/com/mogo/utils/digest/Hex.java | 328 ++++++ .../utils/digest/MessageDigestAlgorithms.java | 70 ++ .../com/mogo/utils/digest/StringEncoder.java | 38 + .../com/mogo/utils/digest/StringUtils.java | 384 +++++++ .../com/mogo/utils/glide/BaseGlideModule.java | 38 + .../java/com/mogo/utils/logger/LogLevel.java | 24 + .../java/com/mogo/utils/logger/Logger.java | 57 + .../com/mogo/utils/logger/LoggerPrinter.java | 261 +++++ .../java/com/mogo/utils/logger/Printer.java | 26 + .../java/com/mogo/utils/logger/Settings.java | 52 + .../com/mogo/utils/network/BaseParams.java | 179 ++++ .../network/CallerNotAliveException.java | 10 + .../mogo/utils/network/CallerRestrictTo.java | 13 + .../com/mogo/utils/network/CallerType.java | 29 + .../com/mogo/utils/network/HttpParams.java | 70 ++ .../com/mogo/utils/network/NetConfig.java | 160 +++ .../com/mogo/utils/network/NetConstants.java | 13 + .../network/NetworkMonitorInterceptor.java | 24 + .../network/NetworkUnavailableException.java | 18 + .../com/mogo/utils/network/OkHttpFactory.java | 55 + .../utils/network/RequestLogInterceptor.java | 46 + .../mogo/utils/network/RequestOptions.java | 127 +++ .../utils/network/ResponseLogInterceptor.java | 59 ++ .../mogo/utils/network/RetrofitFactory.java | 34 + .../com/mogo/utils/network/ServerParam.java | 5 + .../com/mogo/utils/network/SubscriberEx.java | 95 ++ .../mogo/utils/network/ui/ProgressDialog.java | 66 ++ .../network/ui/ProgressDialogFragment.java | 40 + .../mogo/utils/network/utils/GsonUtil.java | 77 ++ .../mogo/utils/network/utils/SignUtil.java | 120 +++ .../com/mogo/utils/network/utils/Util.java | 94 ++ .../mogo/utils/storage/SharedPrefsMgr.java | 131 +++ .../src/main/res/values/strings.xml | 3 + .../java/com/mogo/utils/ExampleUnitTest.java | 17 + gradle.properties | 20 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++ gradlew.bat | 84 ++ libraries/mogo-map/.gitignore | 1 + libraries/mogo-map/build.gradle | 35 + libraries/mogo-map/consumer-rules.pro | 0 libraries/mogo-map/proguard-rules.pro | 21 + .../mogo-map/src/main/AndroidManifest.xml | 2 + .../main/java/com/mogo/map/ILifeCycle.java | 25 + .../src/main/java/com/mogo/map/IMogoMap.java | 99 ++ .../main/java/com/mogo/map/IMogoMapView.java | 21 + .../main/java/com/mogo/map/IUiSettings.java | 68 ++ .../java/com/mogo/map/MogoBaseMapView.java | 114 ++ .../main/java/com/mogo/map/MogoLatLng.java | 49 + .../src/main/java/com/mogo/map/MogoMap.java | 44 + .../main/java/com/mogo/map/MogoMapView.java | 35 + .../mogo/map/amap/AMapNaviViewWrapper.java | 78 ++ .../mogo/map/amap/AMapUiSettingsWrapper.java | 104 ++ .../java/com/mogo/map/amap/AMapWrapper.java | 153 +++ .../map/amap/location/LocationClient.java | 116 +++ .../map/amap/marker/AMapMarkerWrapper.java | 190 ++++ .../map/amap/search/GeocodeSearchClient.java | 101 ++ .../mogo/map/amap/search/InputtipsSearch.java | 84 ++ .../com/mogo/map/amap/utils/ObjectUtils.java | 466 +++++++++ .../mogo/map/exception/MogoMapException.java | 25 + .../mogo/map/location/ILocationListener.java | 17 + .../map/location/IMogoLocationClient.java | 43 + .../com/mogo/map/location/MogoLocation.java | 362 +++++++ .../mogo/map/location/MogoLocationClient.java | 75 ++ .../java/com/mogo/map/marker/IMogoMarker.java | 136 +++ .../java/com/mogo/map/marker/MogoMarker.java | 10 + .../mogo/map/marker/MogoMarkerOptions.java | 222 ++++ .../com/mogo/map/search/IMogoGeoSearch.java | 55 + .../map/search/IMogoGeoSearchListener.java | 26 + .../map/search/IMogoInputtipsListener.java | 14 + .../mogo/map/search/IMogoInputtipsSearch.java | 24 + .../java/com/mogo/map/search/MogoAoiItem.java | 99 ++ .../com/mogo/map/search/MogoBusinessArea.java | 66 ++ .../com/mogo/map/search/MogoCrossroad.java | 108 ++ .../com/mogo/map/search/MogoGeoSearch.java | 62 ++ .../mogo/map/search/MogoGeocodeAddress.java | 103 ++ .../mogo/map/search/MogoGeocodeResult.java | 23 + .../com/mogo/map/search/MogoIndoorData.java | 75 ++ .../mogo/map/search/MogoInputtipsSearch.java | 53 + .../java/com/mogo/map/search/MogoPhoto.java | 64 ++ .../java/com/mogo/map/search/MogoPoiItem.java | 355 +++++++ .../mogo/map/search/MogoPoiItemExtension.java | 63 ++ .../mogo/map/search/MogoRegeocodeAddress.java | 231 +++++ .../mogo/map/search/MogoRegeocodeResult.java | 53 + .../mogo/map/search/MogoRegeocodeRoad.java | 98 ++ .../com/mogo/map/search/MogoStreetNumber.java | 98 ++ .../com/mogo/map/search/MogoSubPoiItem.java | 121 +++ .../java/com/mogo/map/search/MogoTip.java | 120 +++ .../map/search/query/MogoGeocodeQuery.java | 63 ++ .../map/search/query/MogoInputtipsQuery.java | 58 ++ .../map/search/query/MogoRegeocodeQuery.java | 88 ++ .../mogo-map/src/main/res/values/strings.xml | 3 + services/mogo-service-api/.gitignore | 1 + services/mogo-service-api/build.gradle | 38 + services/mogo-service-api/consumer-rules.pro | 0 services/mogo-service-api/proguard-rules.pro | 21 + .../service/map/ExampleInstrumentedTest.java | 27 + .../src/main/AndroidManifest.xml | 2 + .../com/mogo/service/MogoServicePaths.java | 25 + .../imageloader/IMogoImageLoaderListener.java | 20 + .../service/imageloader/IMogoImageloader.java | 28 + .../service/imageloader/MogoImageView.java | 174 ++++ .../com/mogo/service/map/IMogoMapService.java | 64 ++ .../src/main/res/values/attrs.xml | 22 + .../src/main/res/values/strings.xml | 3 + .../com/mogo/service/map/ExampleUnitTest.java | 17 + services/mogo-service/.gitignore | 1 + services/mogo-service/build.gradle | 47 + services/mogo-service/consumer-rules.pro | 0 services/mogo-service/proguard-rules.pro | 21 + .../service/map/ExampleInstrumentedTest.java | 27 + .../mogo-service/src/main/AndroidManifest.xml | 2 + .../impl/imageloader/MogoImageLoader.java | 69 ++ .../imageloader/glide/GlideImageLoader.java | 169 +++ .../glide/transform/GlideBlurTransform.java | 39 + .../transform/GlideCircleBitmapTransform.java | 68 ++ .../transform/GlideRoundBitmapTransform.java | 69 ++ .../imageloader/glide/utils/DiskLruCache.java | 974 ++++++++++++++++++ .../glide/utils/DiskLruCacheManager.java | 101 ++ .../imageloader/glide/utils/FastBlurUtil.java | 241 +++++ .../glide/utils/StrictLineReader.java | 191 ++++ .../impl/imageloader/glide/utils/Util.java | 76 ++ .../mogo/service/impl/map/MogoMapService.java | 68 ++ .../src/main/res/values/strings.xml | 3 + .../com/mogo/service/map/ExampleUnitTest.java | 17 + settings.gradle | 7 + 210 files changed, 17746 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/mogo/launcher/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/mogo/launcher/MogoApplication.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/com/mogo/launcher/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 config.gradle create mode 100644 foudations/mogo-commons/.gitignore create mode 100644 foudations/mogo-commons/build.gradle create mode 100644 foudations/mogo-commons/consumer-rules.pro create mode 100644 foudations/mogo-commons/proguard-rules.pro create mode 100644 foudations/mogo-commons/src/main/AndroidManifest.xml create mode 100644 foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java create mode 100644 foudations/mogo-commons/src/main/java/com/mogo/commons/data/BaseData.java create mode 100644 foudations/mogo-commons/src/main/java/com/mogo/commons/network/AllAllowedHostnameVerifier.java create mode 100644 foudations/mogo-commons/src/main/java/com/mogo/commons/network/X509TrustManagerImpl.java create mode 100644 foudations/mogo-commons/src/main/java/com/mogo/commons/voice/AIAssist.java create mode 100644 foudations/mogo-commons/src/main/java/com/mogo/commons/voice/IMogoVoiceCmdCallBack.java create mode 100644 foudations/mogo-commons/src/main/res/values/colors.xml create mode 100644 foudations/mogo-commons/src/main/res/values/strings.xml create mode 100644 foudations/mogo-utils/.gitignore create mode 100644 foudations/mogo-utils/build.gradle create mode 100644 foudations/mogo-utils/consumer-rules.pro create mode 100644 foudations/mogo-utils/proguard-rules.pro create mode 100644 foudations/mogo-utils/src/androidTest/java/com/mogo/utils/ExampleInstrumentedTest.java create mode 100644 foudations/mogo-utils/src/main/AndroidManifest.xml create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityLifecycleManager.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityStack.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ArrayUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/BitmapHelper.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/CheckUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/CommonUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/CountDownTimer.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/DateTimeUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/DeviceIdUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/FileUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/IOUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/IntentUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/MapUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/MatcherUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ProcessUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ReflectUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ResourcesHelper.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/SoftKeyboardUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ThreadPoolService.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/TipToast.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/UiThreadHandler.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/ValidateUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/WindowUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryDecoder.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryEncoder.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharEncoding.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharSequenceUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Charsets.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Decoder.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DecoderException.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DigestUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Encoder.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/EncoderException.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Hex.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/MessageDigestAlgorithms.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringEncoder.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringUtils.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/glide/BaseGlideModule.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/BaseParams.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerNotAliveException.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerRestrictTo.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerType.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/HttpParams.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConfig.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConstants.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkMonitorInterceptor.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkUnavailableException.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/OkHttpFactory.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestLogInterceptor.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestOptions.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/ResponseLogInterceptor.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/RetrofitFactory.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/ServerParam.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/SubscriberEx.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialog.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialogFragment.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/GsonUtil.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/SignUtil.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/Util.java create mode 100644 foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java create mode 100644 foudations/mogo-utils/src/main/res/values/strings.xml create mode 100644 foudations/mogo-utils/src/test/java/com/mogo/utils/ExampleUnitTest.java create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 libraries/mogo-map/.gitignore create mode 100644 libraries/mogo-map/build.gradle create mode 100644 libraries/mogo-map/consumer-rules.pro create mode 100644 libraries/mogo-map/proguard-rules.pro create mode 100644 libraries/mogo-map/src/main/AndroidManifest.xml create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/ILifeCycle.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/IMogoMap.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/IMogoMapView.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/IUiSettings.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/MogoBaseMapView.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/MogoLatLng.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/MogoMap.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/MogoMapView.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapNaviViewWrapper.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapUiSettingsWrapper.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapWrapper.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/location/LocationClient.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/marker/AMapMarkerWrapper.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/search/GeocodeSearchClient.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/search/InputtipsSearch.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/amap/utils/ObjectUtils.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/exception/MogoMapException.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/location/ILocationListener.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/location/IMogoLocationClient.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocation.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocationClient.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/marker/IMogoMarker.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarker.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarkerOptions.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearch.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearchListener.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsListener.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsSearch.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoAoiItem.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoBusinessArea.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoCrossroad.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeoSearch.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeAddress.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeResult.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoIndoorData.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoInputtipsSearch.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPhoto.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItem.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItemExtension.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeAddress.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeResult.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeRoad.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoStreetNumber.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoSubPoiItem.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/MogoTip.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoGeocodeQuery.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoInputtipsQuery.java create mode 100644 libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoRegeocodeQuery.java create mode 100644 libraries/mogo-map/src/main/res/values/strings.xml create mode 100644 services/mogo-service-api/.gitignore create mode 100644 services/mogo-service-api/build.gradle create mode 100644 services/mogo-service-api/consumer-rules.pro create mode 100644 services/mogo-service-api/proguard-rules.pro create mode 100644 services/mogo-service-api/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java create mode 100644 services/mogo-service-api/src/main/AndroidManifest.xml create mode 100644 services/mogo-service-api/src/main/java/com/mogo/service/MogoServicePaths.java create mode 100644 services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageLoaderListener.java create mode 100644 services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageloader.java create mode 100644 services/mogo-service-api/src/main/java/com/mogo/service/imageloader/MogoImageView.java create mode 100644 services/mogo-service-api/src/main/java/com/mogo/service/map/IMogoMapService.java create mode 100644 services/mogo-service-api/src/main/res/values/attrs.xml create mode 100644 services/mogo-service-api/src/main/res/values/strings.xml create mode 100644 services/mogo-service-api/src/test/java/com/mogo/service/map/ExampleUnitTest.java create mode 100644 services/mogo-service/.gitignore create mode 100644 services/mogo-service/build.gradle create mode 100644 services/mogo-service/consumer-rules.pro create mode 100644 services/mogo-service/proguard-rules.pro create mode 100644 services/mogo-service/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java create mode 100644 services/mogo-service/src/main/AndroidManifest.xml create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/MogoImageLoader.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/GlideImageLoader.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideBlurTransform.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideCircleBitmapTransform.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideRoundBitmapTransform.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCache.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCacheManager.java create mode 100755 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/FastBlurUtil.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/StrictLineReader.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/Util.java create mode 100644 services/mogo-service/src/main/java/com/mogo/service/impl/map/MogoMapService.java create mode 100644 services/mogo-service/src/main/res/values/strings.xml create mode 100644 services/mogo-service/src/test/java/com/mogo/service/map/ExampleUnitTest.java create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..603b140773 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..681f41ae2a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000000..d291b3d7cd --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..29bb4c57c1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000000..7f68460d8b --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000000..f266ec223b --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + buildToolsVersion rootProject.ext.android.buildToolsVersion + defaultConfig { + applicationId rootProject.ext.android.applicationId + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode rootProject.ext.android.versionCode + versionName rootProject.ext.android.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation rootProject.ext.dependencies.androidxappcompat + implementation rootProject.ext.dependencies.androidxconstraintlayout + testImplementation rootProject.ext.dependencies.junit + androidTestImplementation rootProject.ext.dependencies.androidxjunit + androidTestImplementation rootProject.ext.dependencies.androidxespressocore +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/mogo/launcher/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/mogo/launcher/ExampleInstrumentedTest.java new file mode 100644 index 0000000000..2784cd1c3f --- /dev/null +++ b/app/src/androidTest/java/com/mogo/launcher/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.mogo.launcher; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith( AndroidJUnit4.class ) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals( "com.mogo.launcher", appContext.getPackageName() ); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a1d4aa5c1c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/mogo/launcher/MogoApplication.java b/app/src/main/java/com/mogo/launcher/MogoApplication.java new file mode 100644 index 0000000000..7a7ac48037 --- /dev/null +++ b/app/src/main/java/com/mogo/launcher/MogoApplication.java @@ -0,0 +1,17 @@ +package com.mogo.launcher; + +import android.app.Application; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * Launcher application + */ +public class MogoApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..1f6bb29060 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..0d025f9bf6 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..eca70cfe52 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..eca70cfe52 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..898f3ed59ac9f3248734a00e5902736c9367d455 GIT binary patch literal 2963 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 JPDHLkV1g@XpTGbB literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dffca3601eba7bf5f409bdd520820e2eb5122c75 GIT binary patch literal 4905 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 lV%Z2*hu);6rydA(}wUDXPCF_W1vnaRBK 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..14ed0af35023e4f1901cf03487b6c524257b8483 GIT binary patch literal 6895 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b0907cac3bfd8fbfdc46e1108247f0a1055387ec GIT binary patch literal 6387 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..d8ae03154975f397f8ed1b84f2d4bf9783ecfa26 GIT binary patch literal 10413 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 XdJ+W}DiIkV00000NkvXXu0mjfKBlg6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2c18de9e66108411737e910f5c1972476f03ddbf GIT binary patch literal 9128 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 I20H400LcNok^lez literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..beed3cdd2c32af5114a7dc70b9ef5b698eb8797e GIT binary patch literal 15132 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000..69b22338c6 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..146d275293 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + MogoLauncher + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..e6b4523fd9 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/app/src/test/java/com/mogo/launcher/ExampleUnitTest.java b/app/src/test/java/com/mogo/launcher/ExampleUnitTest.java new file mode 100644 index 0000000000..cde4cc10d4 --- /dev/null +++ b/app/src/test/java/com/mogo/launcher/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.mogo.launcher; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals( 4, 2 + 2 ); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..53d9475dce --- /dev/null +++ b/build.gradle @@ -0,0 +1,45 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +apply from: "config.gradle" +buildscript { + ext.kotlin_version = '1.3.61+' + repositories { + maven { + url 'http://maven.aliyun.com/nexus/content/groups/public/' + } + maven { + url 'http://nexus.zhidaoauto.com/repository/maven-releases/' + } + maven { + url 'http://nexus.zhidaoauto.com/repository/maven-public/' + } + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + maven { + url 'http://maven.aliyun.com/nexus/content/groups/public/' + } + maven { + url 'http://nexus.zhidaoauto.com/repository/maven-releases/' + } + maven { + url 'http://nexus.zhidaoauto.com/repository/maven-public/' + } + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/config.gradle b/config.gradle new file mode 100644 index 0000000000..5d361f9b3d --- /dev/null +++ b/config.gradle @@ -0,0 +1,53 @@ +ext { + android = [ + applicationId : "com.mogo.launcher", + compileSdkVersion: 29, + buildToolsVersion: "29.0.2", + minSdkVersion : 19, + targetSdkVersion : 22, + versionCode : 1, + versionName : "1.0.0", + ] + + dependencies = [ + // androidx + androidxappcompat : "androidx.appcompat:appcompat:1.0.2", + androidxconstraintlayout: "androidx.constraintlayout:constraintlayout:1.1.3", + // 测试 + junit : "junit:junit:4.12", + androidxjunit : "androidx.test.ext:junit:1.1.0", + androidxespressocore : "androidx.test.espresso:espresso-core:3.1.1", + // 地图 + amapnavi3dmap : "com.amap.api:navi-3dmap:latest.integration", + amapsearch : "com.amap.api:search:latest.integration", + amaplocation : "com.amap.api:location:latest.integration", + // json 转换 + gson : "com.google.code.gson:gson:2.8.4", + // 内存泄漏检测 + debugleakcanary : "com.squareup.leakcanary:leakcanary-android:1.6.1", + releaseleakcanary : "com.squareup.leakcanary:leakcanary-android-no-op:1.6.1", + testleakcanary : "com.squareup.leakcanary:leakcanary-android-no-op:1.6.1", + //rxJava + rxjava : "io.reactivex.rxjava2:rxjava:2.2.2", + rxandroid : "io.reactivex.rxjava2:rxandroid:2.1.0", + // arouter + arouter : "com.alibaba:arouter-api:1.5.0", + aroutercompiler : "com.alibaba:arouter-compiler:1.2.2", + // glide + glide : 'com.github.bumptech.glide:glide:4.8.0', + glideokhttp3 : 'com.github.bumptech.glide:okhttp3-integration:4.8.0', + glideanno : 'com.github.bumptech.glide:annotations:4.8.0', + glidecompiler : 'com.github.bumptech.glide:compiler:4.8.0', + supportannos : "com.android.support:support-annotations:28.0.0", + // fresco + fresco : 'com.facebook.fresco:fresco:1.1.0', + // 公司服务 - 语音 + aiassist : "com.zhidaoauto.common:service:1.0.4", + + // retrofit + retrofit : "com.squareup.retrofit2:retrofit:2.3.0", + retrofitadapter : "com.squareup.retrofit2:adapter-rxjava:2.1.0", + retrofitconvertergson : "com.squareup.retrofit2:converter-gson:2.3.0", + retrofitconverterscalars: "com.squareup.retrofit2:converter-scalars:2.1.0", + ] +} \ No newline at end of file diff --git a/foudations/mogo-commons/.gitignore b/foudations/mogo-commons/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/foudations/mogo-commons/.gitignore @@ -0,0 +1 @@ +/build diff --git a/foudations/mogo-commons/build.gradle b/foudations/mogo-commons/build.gradle new file mode 100644 index 0000000000..351e0d375c --- /dev/null +++ b/foudations/mogo-commons/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + api rootProject.ext.dependencies.aiassist + implementation project(":foudations:mogo-utils") +} diff --git a/foudations/mogo-commons/consumer-rules.pro b/foudations/mogo-commons/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/foudations/mogo-commons/proguard-rules.pro b/foudations/mogo-commons/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/foudations/mogo-commons/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/foudations/mogo-commons/src/main/AndroidManifest.xml b/foudations/mogo-commons/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..ddb5e04218 --- /dev/null +++ b/foudations/mogo-commons/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java new file mode 100644 index 0000000000..eeb155ead3 --- /dev/null +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java @@ -0,0 +1,24 @@ +package com.mogo.commons; + +import android.app.Application; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 描述 + */ +public class AbsMogoApplication extends Application { + + private static Application sApp; + + @Override + public void onCreate() { + super.onCreate(); + sApp = this; + } + + public static Application getApp() { + return sApp; + } +} diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/data/BaseData.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/data/BaseData.java new file mode 100644 index 0000000000..82693e807d --- /dev/null +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/data/BaseData.java @@ -0,0 +1,12 @@ +package com.mogo.commons.data; + +import java.io.Serializable; + +/** + * Created by congtaowang on 2019/1/7. + */ +public class BaseData implements Serializable, Cloneable { + + public int code = -1; + public String msg; +} diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/network/AllAllowedHostnameVerifier.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/network/AllAllowedHostnameVerifier.java new file mode 100644 index 0000000000..21368f008f --- /dev/null +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/network/AllAllowedHostnameVerifier.java @@ -0,0 +1,18 @@ +package com.mogo.commons.network; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +/** + * @author congtaowang + * @since 2019-08-30 + *

+ * 信任所有域名 + */ +public class AllAllowedHostnameVerifier implements HostnameVerifier { + + @Override + public boolean verify( String hostname, SSLSession session ) { + return true; + } +} diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/network/X509TrustManagerImpl.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/network/X509TrustManagerImpl.java new file mode 100644 index 0000000000..7fda1722cc --- /dev/null +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/network/X509TrustManagerImpl.java @@ -0,0 +1,30 @@ +package com.mogo.commons.network; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +/** + * @author congtaowang + * @since 2019-08-30 + *

+ * 描述 + */ +public class X509TrustManagerImpl implements X509TrustManager { + + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException { + + } + + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType ) throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/voice/AIAssist.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/voice/AIAssist.java new file mode 100644 index 0000000000..7f50aeae8e --- /dev/null +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/voice/AIAssist.java @@ -0,0 +1,102 @@ +package com.mogo.commons.voice; + +import android.content.Context; + +import com.zhidao.auto.platform.voice.VoiceClient; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author congtaowang + * @since 2019-10-03 + *

+ * 语音助手通信助手 + */ +public class AIAssist implements VoiceClient.VoiceCmdCallBack { + + private static volatile AIAssist sInstance; + + public static AIAssist getInstance( Context context ) { + if ( sInstance == null ) { + synchronized ( AIAssist.class ) { + if ( sInstance == null ) { + sInstance = new AIAssist( context ); + } + } + } + return sInstance; + } + + public synchronized void release() { + sInstance = null; + } + + private final VoiceClient mVoiceClient; + private Map< String, IMogoVoiceCmdCallBack > mUnWakeupCmdMap = new HashMap<>(); + + private AIAssist( Context context ) { + // private constructor + mVoiceClient = new VoiceClient( context.getApplicationContext() ); + mVoiceClient.setCallBack( this ); + } + + @Override + public void onCmdSelected( String cmd ) { + final IMogoVoiceCmdCallBack cmdCallBack = mUnWakeupCmdMap.get( cmd ); + if ( cmdCallBack != null ) { + cmdCallBack.onCmdSelected( cmd ); + } + } + + @Override + public void onCmdAction( String speakText ) { + + } + + @Override + public void onCmdCancel( String speakText ) { + + } + + @Override + public void onSpeakEnd( String speakText ) { + IMogoVoiceCmdCallBack callBack = mUnWakeupCmdMap.get( speakText ); + if ( callBack != null ) { + callBack.onSpeakEnd( speakText ); + } + } + + @Override + public void onSpeakSelectTimeOut( String speakText ) { + IMogoVoiceCmdCallBack callBack = mUnWakeupCmdMap.get( speakText ); + if ( callBack != null ) { + callBack.onSpeakSelectTimeOut( speakText ); + } + } + + public void speakTTSVoice( String text ) { + try { + mVoiceClient.speakDefault( text ); + } catch ( Exception e ) { + } + } + + public void registerUnWakeupCommand( String cmd, String[] cmdWords, IMogoVoiceCmdCallBack callBack ) { + mUnWakeupCmdMap.put( cmd, callBack ); + mVoiceClient.registerCustomWakeupCmd( cmd, cmdWords ); + } + + public void unregisterUnWakeupCommand( String cmd ) { + mUnWakeupCmdMap.remove( cmd ); + mVoiceClient.unRegisterCustomWakeupCmd( cmd ); + } + + public void registerTTSCallback( String tts, IMogoVoiceCmdCallBack cmdCallBack ) { + mUnWakeupCmdMap.put( tts, cmdCallBack ); + } + + public void unregisterTTSCallback( String tts ) { + mUnWakeupCmdMap.remove( tts ); + } +} diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/voice/IMogoVoiceCmdCallBack.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/voice/IMogoVoiceCmdCallBack.java new file mode 100644 index 0000000000..b213d117fa --- /dev/null +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/voice/IMogoVoiceCmdCallBack.java @@ -0,0 +1,38 @@ +package com.mogo.commons.voice; + +public interface IMogoVoiceCmdCallBack { + /** + * 免唤醒命令响应回调 + * + * @param cmd + */ + void onCmdSelected( String cmd ); + + /** + * 语音播报临时免唤醒“确定”命令 + * + * @param speakText 播报内容 + */ + void onCmdAction( String speakText ); + + /** + * 语音播报临时免唤醒“取消”命令 + * + * @param speakText 播报内容 + */ + void onCmdCancel( String speakText ); + + /** + * 语音播报完毕 + * + * @param speakText 播报内容 + */ + void onSpeakEnd( String speakText ); + + /** + * 语音播报完临时命令选择超时 + * + * @param speakText 播报内容 + */ + void onSpeakSelectTimeOut( String speakText ); +} \ No newline at end of file diff --git a/foudations/mogo-commons/src/main/res/values/colors.xml b/foudations/mogo-commons/src/main/res/values/colors.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/foudations/mogo-commons/src/main/res/values/colors.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/foudations/mogo-commons/src/main/res/values/strings.xml b/foudations/mogo-commons/src/main/res/values/strings.xml new file mode 100644 index 0000000000..0bfdd06f8b --- /dev/null +++ b/foudations/mogo-commons/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + mogo-commons + diff --git a/foudations/mogo-utils/.gitignore b/foudations/mogo-utils/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/foudations/mogo-utils/.gitignore @@ -0,0 +1 @@ +/build diff --git a/foudations/mogo-utils/build.gradle b/foudations/mogo-utils/build.gradle new file mode 100644 index 0000000000..11eb8bcdca --- /dev/null +++ b/foudations/mogo-utils/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + api rootProject.ext.dependencies.glide + implementation rootProject.ext.dependencies.glideanno + implementation rootProject.ext.dependencies.glideokhttp3 + implementation rootProject.ext.dependencies.supportannos + annotationProcessor rootProject.ext.dependencies.supportannos + annotationProcessor rootProject.ext.dependencies.glidecompiler + api rootProject.ext.dependencies.retrofit + api rootProject.ext.dependencies.retrofitadapter + api rootProject.ext.dependencies.retrofitconvertergson + api rootProject.ext.dependencies.retrofitconverterscalars + implementation rootProject.ext.dependencies.androidxappcompat +} diff --git a/foudations/mogo-utils/consumer-rules.pro b/foudations/mogo-utils/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/foudations/mogo-utils/proguard-rules.pro b/foudations/mogo-utils/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/foudations/mogo-utils/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/foudations/mogo-utils/src/androidTest/java/com/mogo/utils/ExampleInstrumentedTest.java b/foudations/mogo-utils/src/androidTest/java/com/mogo/utils/ExampleInstrumentedTest.java new file mode 100644 index 0000000000..dca1040e3f --- /dev/null +++ b/foudations/mogo-utils/src/androidTest/java/com/mogo/utils/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.mogo.utils; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith( AndroidJUnit4.class ) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals( "com.mogo.utils.test", appContext.getPackageName() ); + } +} diff --git a/foudations/mogo-utils/src/main/AndroidManifest.xml b/foudations/mogo-utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..7447df8a92 --- /dev/null +++ b/foudations/mogo-utils/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityLifecycleManager.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityLifecycleManager.java new file mode 100644 index 0000000000..5297a356e0 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityLifecycleManager.java @@ -0,0 +1,335 @@ +package com.mogo.utils; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +import java.util.ArrayList; +import java.util.HashMap; + +public class ActivityLifecycleManager { + private Application mApplication; + /** + * 当前出于 Start 状态的 Activity + */ + private ArrayList< Activity > mStartedActivities = new ArrayList<>(); + + private ArrayList mAppStateListeners = new ArrayList<>(); + + /** + * 当前存活的 Activity + */ + private HashMap< Activity, ActivityTrace> mAliveActivities = new HashMap<>(); + + /** + * App 是否出于前台 + */ + private volatile boolean mIsAppActive = false; + + private Activity mCurrentResumedActivity; + + /** + * Home 键事件广播的接受器 + */ + private HomeKeyEventReceiver mHomeKeyEventReceiver; + /** + * Home 键事件监听者列表 + */ + private ArrayList mHomeKeyEventListeners = new ArrayList<>(); + + private DefActivityLifecycleCallbacks mInnerActivityListener = new DefActivityLifecycleCallbacks() { + @Override + public void onActivityStarted( Activity activity) { + if (mStartedActivities.isEmpty()) { + mIsAppActive = true; + notifyAppStateChanged( AppStateListener.ACTIVE); + } + mStartedActivities.add(activity); + + ActivityTrace trace = mAliveActivities.get(activity); + if (trace != null) { + trace.startCnt++; + } + } + + @Override + public void onActivityStopped( Activity activity) { + mStartedActivities.remove(activity); + if (mStartedActivities.isEmpty()) { + mIsAppActive = false; + notifyAppStateChanged( AppStateListener.INACTIVE); + } + + ActivityTrace trace = mAliveActivities.get(activity); + if (trace != null) { + trace.stopCnt++; + } + } + + @Override + public void onActivityResumed( Activity activity) { + mCurrentResumedActivity = activity; + ActivityTrace trace = mAliveActivities.get(activity); + if (trace != null) { + trace.resumeCnt++; + } + } + + @Override + public void onActivityPaused( Activity activity) { + ActivityTrace trace = mAliveActivities.get(activity); + if (trace != null) { + trace.pauseCnt++; + } + } + + @Override + public void onActivityCreated( Activity activity, Bundle savedInstanceState) { + mAliveActivities.put(activity, new ActivityTrace(activity)); + } + + @Override + public void onActivityDestroyed( Activity activity) { + mAliveActivities.remove(activity); + } + }; + + private ActivityLifecycleManager() {} + + public static ActivityLifecycleManager getInstance() { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder{ + private static final ActivityLifecycleManager INSTANCE = new ActivityLifecycleManager(); + } + + public void start( Application application) { + this.mApplication = application; + registerInnerActivityListener(); + registerHomeKeyEventReceiver(); + } + + public void stop(){ + unregisterActivityLifecycleCallbacks(mInnerActivityListener); + unregisterHomeKeyEventReceiver(); + } + + /** + * 注册 Activity 生命周期的回调 + * + * @param callbacks Activity 生命周期的回调 + */ + public void registerActivityLifecycleCallbacks( Application.ActivityLifecycleCallbacks callbacks) { + if (mApplication == null) { + return; + } + + if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return; + } + + mApplication.registerActivityLifecycleCallbacks(callbacks); + } + + /** + * 取消注册 Activity 生命周期的回调 + * + * @param callbacks Activity 生命周期的回调 + */ + public void unregisterActivityLifecycleCallbacks( Application.ActivityLifecycleCallbacks callbacks) { + if (mApplication == null) { + return; + } + + if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return; + } + + mApplication.unregisterActivityLifecycleCallbacks(callbacks); + } + + /** + * 应用是否出于前台 + */ + public boolean isAppActive() { + return mIsAppActive; + } + + public Activity getCurrentActivity(){ + return mCurrentResumedActivity; + } + + /** + * 添加应用状态的监听 + */ + public void addAppStateListener(AppStateListener listener) { + synchronized (mAppStateListeners) { + mAppStateListeners.add(listener); + } + } + + /** + * 移除应用状态的监听 + * + * @param listener + */ + public void removeAppStateListener(AppStateListener listener) { + synchronized (mAppStateListeners) { + mAppStateListeners.remove(listener); + } + } + + /** + * 添加 home 键的事件监听 + */ + public void addHomeKeyEventListener(HomeKeyEventListener listener) { + if (listener == null) { + return; + } + synchronized (mHomeKeyEventListeners) { + mHomeKeyEventListeners.add(listener); + } + } + + /** + * 移除 home 键的事件监听 + */ + public void removeHomeKeyEventListener(HomeKeyEventListener listener) { + if (listener == null) { + return; + } + + synchronized (mHomeKeyEventListeners) { + mHomeKeyEventListeners.remove(listener); + } + } + + /** + * 注册Activity生命周期的监听 + */ + private void registerInnerActivityListener() { + registerActivityLifecycleCallbacks(mInnerActivityListener); + } + + private void notifyAppStateChanged(int state) { + Object[] listeners = collectAppStateListeners(); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + ((AppStateListener) listeners[i]).onStateChanged(state); + } + } + } + + private Object[] collectAppStateListeners() { + Object[] listeners = null; + synchronized (mAppStateListeners) { + if (mAppStateListeners.size() > 0) { + listeners = mAppStateListeners.toArray(); + } + } + return listeners; + } + + private Object[] collectHomeKeyEventListeners() { + Object[] listeners = null; + synchronized (mHomeKeyEventListeners) { + if (mHomeKeyEventListeners.size() > 0) { + listeners = mHomeKeyEventListeners.toArray(); + } + } + return listeners; + } + + private void registerHomeKeyEventReceiver() { + android.content.IntentFilter filter = new android.content.IntentFilter(); + filter.addAction( Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + + mHomeKeyEventReceiver = new HomeKeyEventReceiver(); + mApplication.registerReceiver(mHomeKeyEventReceiver, filter); + } + + private void unregisterHomeKeyEventReceiver() { + mApplication.unregisterReceiver(mHomeKeyEventReceiver); + } + + private void onHomeKeyPressed() { + Object[] listeners = collectHomeKeyEventListeners(); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + ((HomeKeyEventListener) listeners[i]).onHomeKeyPressed(); + } + } + } + + @TargetApi( Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public static abstract class DefActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { + @Override + public void onActivityCreated( Activity activity, Bundle savedInstanceState) {} + @Override + public void onActivityStarted( Activity activity) {} + @Override + public void onActivityResumed( Activity activity) {} + @Override + public void onActivityPaused( Activity activity) {} + @Override + public void onActivityStopped( Activity activity) {} + @Override + public void onActivitySaveInstanceState( Activity activity, Bundle outState) {} + @Override + public void onActivityDestroyed( Activity activity) {} + } + + public interface AppStateListener { + int INACTIVE = 0; + int ACTIVE = 1; + /** + * App 状态的回调 + */ + void onStateChanged( int state ); + + } + + /** + * home 键的监听 + */ + public interface HomeKeyEventListener { + void onHomeKeyPressed(); + } + + /** + * Activity 的生命周期调用痕迹 + */ + static class ActivityTrace { + Activity activity; + int resumeCnt; + int pauseCnt; + int startCnt; + int stopCnt; + + ActivityTrace( Activity activity) { + this.activity = activity; + } + } + + private final class HomeKeyEventReceiver extends BroadcastReceiver { + private final String SYSTEM_DIALOG_REASON_KEY = "reason"; + private final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; + + @Override + public void onReceive( Context context, Intent intent) { + String action = intent.getAction(); + if ( Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); + if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) { + onHomeKeyPressed(); + } + } + } + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityStack.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityStack.java new file mode 100644 index 0000000000..b315ae569c --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ActivityStack.java @@ -0,0 +1,84 @@ +package com.mogo.utils; + +import android.app.Activity; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +public class ActivityStack { + private static final Deque< Activity > ACTIVITY_STACK = new ArrayDeque<>(); + + public static synchronized void addActivity( Activity activity) { + if(activity != null){ + ACTIVITY_STACK.offer(activity); + } + } + + public static synchronized Activity currentActivity() { + return ACTIVITY_STACK.peekLast(); + } + + public static synchronized void finishCurrentActivity() { + Activity activity = ACTIVITY_STACK.pop(); + if (!activity.isFinishing()) { + activity.finish(); + } + } + + public static synchronized void finishActivity( Activity activity) { + ACTIVITY_STACK.remove(activity); + if (!activity.isFinishing()) { + activity.finish(); + } + } + + public static synchronized void finishActivity( Class cls) { + Iterator< Activity > iterator = ACTIVITY_STACK.iterator(); + while (iterator.hasNext()) { + Activity next = iterator.next(); + if(next.getClass().equals(cls)){ + iterator.remove(); + if(!next.isFinishing()){ + next.finish(); + } + } + } + } + + public static synchronized void finishActivityExcept( Class cls) { + Iterator< Activity > iterator = ACTIVITY_STACK.iterator(); + while (iterator.hasNext()) { + Activity next = iterator.next(); + if (!next.getClass().equals(cls)) { + iterator.remove(); + if(!next.isFinishing()){ + next.finish(); + } + } + } + } + + public static synchronized void finishAllActivity() { + Iterator< Activity > iterator = ACTIVITY_STACK.iterator(); + while (iterator.hasNext()) { + iterator.remove(); + Activity next = iterator.next(); + if(!next.isFinishing()){ + next.finish(); + } + } + } + + public static synchronized boolean isActivityExist( Class cls){ + Iterator< Activity > iterator = ACTIVITY_STACK.iterator(); + while (iterator.hasNext()) { + Activity next = iterator.next(); + if(next.getClass().equals(cls)){ + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ArrayUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ArrayUtils.java new file mode 100644 index 0000000000..1a92a858e2 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ArrayUtils.java @@ -0,0 +1,464 @@ +package com.mogo.utils; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +@SuppressWarnings({ "unchecked" }) +public class ArrayUtils { + + /** + * 加强版的asList会检查传入是否为null,以保证不会抛出异常 并且返回的arraylist改为{@link ArrayList} + * + * @param + * @param array + * @return + */ + public static ArrayList asList( T... array) { + if (array == null) { + return new ArrayList(0); + } + ArrayList list = new ArrayList( Arrays.asList(array)); + return list; + } + + public static List asReadOnlyList( T... array) { + return new ReadOnlyArrayList(array); + } + + public static boolean contains( Object[] array, Object value) { + if (array == null) { + return false; + } + for ( Object object : array) { + if (object == null) { + if (value == null) { + return true; + } + } else if (object.equals(value)) { + return true; + } + } + return false; + } + + /** + * 在已排序数组中查询是否存在某值 + * + * @param array + * @param value + * @return + */ + public static boolean contains(int[] array, int value) { + return Arrays.binarySearch(array, value) >= 0 ? true : false; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with false (if necessary) so the copy has the + * specified length. For all indices that are valid in both the original array and the copy, the two arrays will + * contain identical values. For any indices that are valid in the copy but not the original, the copy will contain + * false. Such indices will exist if and only if the specified length is greater than that of the original + * array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with false elements to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static boolean[] copyOf(boolean[] original, int newLength) { + boolean[] copy = new boolean[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain (byte)0. + * Such indices will exist if and only if the specified length is greater than that of the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with zeros to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static byte[] copyOf(byte[] original, int newLength) { + byte[] copy = new byte[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with null characters (if necessary) so the copy has the + * specified length. For all indices that are valid in both the original array and the copy, the two arrays will + * contain identical values. For any indices that are valid in the copy but not the original, the copy will contain + * '\\u000'. Such indices will exist if and only if the specified length is greater than that of the + * original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with null characters to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static char[] copyOf(char[] original, int newLength) { + char[] copy = new char[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain 0d. Such + * indices will exist if and only if the specified length is greater than that of the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with zeros to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static double[] copyOf(double[] original, int newLength) { + double[] copy = new double[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain 0f. Such + * indices will exist if and only if the specified length is greater than that of the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with zeros to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static float[] copyOf(float[] original, int newLength) { + float[] copy = new float[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain 0. Such + * indices will exist if and only if the specified length is greater than that of the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with zeros to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static int[] copyOf(int[] original, int newLength) { + int[] copy = new int[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain 0L. Such + * indices will exist if and only if the specified length is greater than that of the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with zeros to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static long[] copyOf(long[] original, int newLength) { + long[] copy = new long[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain (short)0. + * Such indices will exist if and only if the specified length is greater than that of the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with zeros to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static short[] copyOf(short[] original, int newLength) { + short[] copy = new short[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain null. + * Such indices will exist if and only if the specified length is greater than that of the original array. The + * resulting array is of exactly the same class as the original array. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @return a copy of the original array, truncated or padded with nulls to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + */ + public static T[] copyOf(T[] original, int newLength) { + return (T[]) copyOf(original, newLength, original.getClass()); + } + + /** + * 从jdk1.6拷贝过来,android中没有这些方法。
+ * Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length. + * For all indices that are valid in both the original array and the copy, the two arrays will contain identical + * values. For any indices that are valid in the copy but not the original, the copy will contain null. + * Such indices will exist if and only if the specified length is greater than that of the original array. The + * resulting array is of the class newType. + * + * @param original the array to be copied + * @param newLength the length of the copy to be returned + * @param newType the class of the copy to be returned + * @return a copy of the original array, truncated or padded with nulls to obtain the specified length + * @throws NegativeArraySizeException if newLength is negative + * @throws NullPointerException if original is null + * @throws ArrayStoreException if an element copied from original is not of a runtime type that can be + * stored in an array of class newType + */ + public static T[] copyOf(U[] original, int newLength, Class newType) { + T[] copy = ( Object ) newType == ( Object ) Object[].class ? (T[]) new Object[newLength] : (T[]) Array.newInstance( + newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * 在数组中查询某值所在位置 + * + * @param array + * @param value + * @return + */ + public static int indexOf(int[] array, int value) { + for (int i = 0; i < array.length; i++) { + if (array[i] == value) { + return i; + } + } + throw new ArrayIndexOutOfBoundsException(value + "is not in " + Arrays.toString(array)); + } + + /** + * 在数组中查询某值所在位置 + * + * @param + * @param array + * @param value + * @return + */ + public static int indexOf(T[] array, T value) { + for (int i = 0; i < array.length; i++) { + if (array[i].equals(value)) { + return i; + } + } + throw new ArrayIndexOutOfBoundsException(value.toString() + "is not in " + Arrays.toString(array)); + } + + private static class ReadOnlyArrayList extends AbstractList implements List, Serializable, RandomAccess { + + private static final long serialVersionUID = 1L; + private final E[] a; + + ReadOnlyArrayList(E[] storage) { + a = storage; + } + + @Override + public boolean contains( Object object) { + if (a == null) { + return false; + } + if (object != null) { + for (E element : a) { + if (object.equals(element)) { + return true; + } + } + } else { + for (E element : a) { + if (element == null) { + return true; + } + } + } + return false; + } + + @Override + public E get(int location) { + try { + return a[location]; + } catch ( ArrayIndexOutOfBoundsException e) { + throw new IndexOutOfBoundsException(); + } catch ( NullPointerException e) { + throw new IndexOutOfBoundsException(); + } + } + + @Override + public int indexOf( Object object) { + if (a == null) { + return -1; + } + if (object != null) { + for (int i = 0; i < a.length; i++) { + if (object.equals(a[i])) { + return i; + } + } + } else { + for (int i = 0; i < a.length; i++) { + if (a[i] == null) { + return i; + } + } + } + return -1; + } + + @Override + public int lastIndexOf( Object object) { + if (a == null) { + return -1; + } + if (object != null) { + for (int i = a.length - 1; i >= 0; i--) { + if (object.equals(a[i])) { + return i; + } + } + } else { + for (int i = a.length - 1; i >= 0; i--) { + if (a[i] == null) { + return i; + } + } + } + return -1; + } + + @Override + public E set(int location, E object) { + if (a == null) { + throw new IndexOutOfBoundsException(); + } + try { + E result = a[location]; + a[location] = object; + return result; + } catch ( ArrayIndexOutOfBoundsException e) { + throw new IndexOutOfBoundsException(); + } catch ( ArrayStoreException e) { + throw new ClassCastException(); + } + } + + @Override + public int size() { + return a == null ? 0 : a.length; + } + + @Override + public Object[] toArray() { + if (a == null) { + return new Object[0]; + } + return a.clone(); + } + + @Override + public T[] toArray(T[] contents) { + if (a == null) { + return contents; + } + int size = size(); + if (size > contents.length) { + Class ct = contents.getClass().getComponentType(); + contents = (T[]) Array.newInstance(ct, size); + } + System.arraycopy(a, 0, contents, 0, size); + if (size < contents.length) { + contents[size] = null; + } + return contents; + } + } + + /** + * @param array + * @return + */ + public static boolean isEmpty( Collection array) { + if (array == null || array.size() == 0) { + return true; + } + return false; + } + + /** + * @param array + * @return + */ + public static boolean isEmpty(T[] array) { + if (array == null || array.length == 0) { + return true; + } + return false; + } + + /** + * 合并2个array + * + * @param head + * @param tail + * @return + */ + public static T[] join(T[] head, T[] tail) { + if (head == null) { + return tail; + } + if (tail == null) { + return head; + } + Class type = head.getClass().getComponentType(); + T[] result = (T[]) Array.newInstance(type, head.length + tail.length); + + System.arraycopy(head, 0, result, 0, head.length); + System.arraycopy(tail, 0, result, head.length, tail.length); + + return result; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/BitmapHelper.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/BitmapHelper.java new file mode 100644 index 0000000000..a2a84d73ac --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/BitmapHelper.java @@ -0,0 +1,673 @@ +package com.mogo.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.media.ExifInterface; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.opengl.GLES10; +import android.os.Build; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * @author wangzhiyuan + * @since 2017/8/30 + */ + +public class BitmapHelper { + private static final String TAG = "BitmapHelper"; + + static int ONE_KB = 1024; + static int ONE_MB = ONE_KB * 1024; + + static int SIZE_DEFAULT = 2048; + static int SIZE_LIMIT = 2048; + + /** + * 根据原图添加圆角 + * + * @param source + * @return + */ + public static Bitmap createRoundCornerImage( Bitmap source, float corner ) { + final Paint paint = new Paint(); + paint.setAntiAlias( true ); + Bitmap target = Bitmap.createBitmap( source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888 ); + Canvas canvas = new Canvas( target ); + RectF rect = new RectF( 0, 0, source.getWidth(), source.getHeight() ); + canvas.drawRoundRect( rect, corner, corner, paint ); + paint.setXfermode( new PorterDuffXfermode( PorterDuff.Mode.SRC_IN ) ); + canvas.drawBitmap( source, 0, 0, paint ); + return target; + } + + public static byte[] bitmapToBytes( Bitmap bitmap ) { + if ( bitmap == null ) { + return null; + } + + ByteArrayOutputStream bos = null; + byte[] result = null; + + try { + bos = new ByteArrayOutputStream(); + bitmap.compress( Bitmap.CompressFormat.JPEG, 100, bos ); + result = bos.toByteArray(); + } catch ( Exception e ) { + e.printStackTrace(); + result = null; + } finally { + IOUtils.closeSilently( bos ); + } + + return result; + } + + /** + * Use quality compression to compress bitmap's size to be smaller than a max size, and convert it to bytes thereafter. + * Note that this method will not report compressing ratio related data. + * + * @param bitmap data source + * @param maxSize unit in kb + * @return bytes after compressing bitmap to a size smaller than a specific max size. + */ + public static byte[] bitmapToBytes( Bitmap bitmap, int maxSize ) { + final long start = System.currentTimeMillis(); + + if ( bitmap == null ) { + return null; + } + + final int maxSizeOfBytes = maxSize * ONE_KB; + ByteArrayOutputStream bos = null; + byte[] result = null; + + try { + bos = new ByteArrayOutputStream(); + int quality = 100; + int fullSize = 0; + + do { + bos.reset(); + bitmap.compress( Bitmap.CompressFormat.JPEG, quality, bos ); + if ( quality == 100 ) { + fullSize = bos.size(); + } + Log.i( TAG, "quality<---->size, " + quality + "<---->" + bos.size() / 1024 ); + } + while ( bos.size() > maxSizeOfBytes && ( quality -= ( fullSize > ONE_MB ) ? 10 : 5 ) >= 0 ); + + result = bos.toByteArray(); + + final long end = System.currentTimeMillis(); + Log.i( TAG, + "bitmap to bytes costs " + ( end - start ) + "ms, \n" + + "bitmap full size is " + ( fullSize / 1024 ) + "kb, \n" + + "bitmap final size is " + ( bos.size() / 1024 ) + "kb, \n" + + "bitmap quality is " + quality ); + } catch ( Exception e ) { + e.printStackTrace(); + result = null; + } finally { + IOUtils.closeSilently( bos ); + } + + return result; + } + + /** + * Use quality compression to compress bitmap to be smaller than a specific max size. + * + * @param bitmap data source + * @param maxSize a specific max size which's unit is kb. + * @return a compressed bitmap smaller than the max size. + */ + public static Bitmap compressBitmap( Bitmap bitmap, int maxSize, final OnCompressListener listener ) { + if ( bitmap == null || bitmap.isRecycled() ) { + return null; + } + listener.onBeforeCompress(); + ByteArrayOutputStream bos = null; + Bitmap target = null; + + try { + bos = new ByteArrayOutputStream(); + int quality = 100; + + do { + bos.reset(); + bitmap.compress( Bitmap.CompressFormat.JPEG, quality, bos ); + } + while ( bos.size() / 1024 > maxSize && ( quality -= 5 ) >= 0 ); + + byte[] result = bos.toByteArray(); + target = bytesToBitmap( result ); + if ( listener != null ) { + listener.onCompressSuccess( result ); + } + } catch ( Exception e ) { + e.printStackTrace(); + target = null; + if ( listener != null ) { + listener.onCompressFailed( "压缩失败" ); + } + } finally { + IOUtils.closeSilently( bos ); + } + + return target; + } + + + public static Bitmap compressBitmap( Bitmap bitmap, int maxSize ) { + if ( bitmap == null ) { + return null; + } + + ByteArrayOutputStream bos = null; + Bitmap target = null; + + try { + bos = new ByteArrayOutputStream(); + int quality = 100; + + do { + bos.reset(); + bitmap.compress( Bitmap.CompressFormat.JPEG, quality, bos ); + } + while ( bos.size() / 1024 > maxSize && ( quality -= 5 ) >= 0 ); + + byte[] result = bos.toByteArray(); + target = bytesToBitmap( result ); + } catch ( Exception e ) { + e.printStackTrace(); + target = null; + } finally { + IOUtils.closeSilently( bos ); + } + + return target; + } + + /** + * Decode an immutable bitmap from the specified byte array. + * + * @param b byte array of compressed image data + * @return an immutable bitmap or null in case of exception. + */ + public static Bitmap bytesToBitmap( byte[] b ) { + if ( b != null && b.length != 0 ) { + return BitmapFactory.decodeByteArray( b, 0, b.length ); + } else { + return null; + } + } + + /** + * Decode an immutable bitmap from the specified byte array. + * + * @param b byte array of compressed image data + * @param options Options that control downsampling and whether the + * image should be completely decoded, or just is size returned. + * @return an immutable bitmap or null in case of exception. + */ + public static Bitmap bytesToBitmap( byte[] b, BitmapFactory.Options options ) { + if ( b.length != 0 ) { + return BitmapFactory.decodeByteArray( b, 0, b.length, options ); + } else { + return null; + } + } + + /** + * Get max supported image size which will differ from different devices. + * + * @return max size related to the device. + */ + public static int getMaxSupportedImageSize() { + int textureLimit = getMaxTextureSize(); + if ( textureLimit == 0 ) { + return SIZE_DEFAULT; + } else { + return Math.min( textureLimit, SIZE_LIMIT ); + } + } + + public static int getMaxTextureSize2() { + // The OpenGL texture size is the maximum size that can be drawn in an ImageView + int[] maxSize = new int[1]; + GLES10.glGetIntegerv( GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0 ); + return maxSize[0]; + } + + /** + * Decode a bitmap's input stream to find a proper inSampleSize according to device's max supported size. + * + * @param is bitmap's data source + * @param close whether to close input stream after work is done. + * @return a proper inSampleSize + */ + public static int findProperInSampleSize( InputStream is, boolean close ) { + // Just decode image size into options + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + + try { + BitmapFactory.decodeStream( is, null, options ); + } catch ( Exception e ) { + e.printStackTrace(); + } finally { + if ( close ) IOUtils.closeSilently( is ); + } + + int maxSize = getMaxSupportedImageSize(); + int sampleSize = 1; + + while ( options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize ) { + sampleSize = sampleSize << 1; + } + + Log.i( TAG, "sample size is " + sampleSize ); + return sampleSize; + } + + /** + * Read a picture's degree from a file. + * + * @param file data source of a picture + * @return degrees range from 0 to 360 + */ + public static int readPictureDegree( File file ) { + return readPictureDegree( file.getAbsolutePath() ); + } + + /** + * Read a picture's degree from a file, we use {@link ExifInterface} instead of {@link android.media.ExifInterface} + * to avoid some unexpected bugs. + * + * @param filePath file's absolute path which we can read data source of a picture from. + * @return degrees range from 0 to 360 + */ + public static int readPictureDegree( String filePath ) { + int degree = 0; + + try { + ExifInterface exifInterface = new ExifInterface( filePath ); + int orientation = exifInterface.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL ); + switch ( orientation ) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch ( Exception e ) { + e.printStackTrace(); + } + Log.i( TAG, "ExifInterface, degree is " + degree ); + + return degree; + } + + /** + * Rotate an bitmap to a specific angle. + * + * @param angle target angle + * @param bitmap data source + * @return Returns an immutable bitmap from subset of the source bitmap, + * transformed by the optional matrix. The new bitmap may be the + * same object as source, or a copy may have been made. It is + * initialized with the same density as the original bitmap. + *

+ * If the source bitmap is immutable and the requested subset is the + * same as the source bitmap itself, then the source bitmap is + * returned and no new bitmap is created. + */ + public static Bitmap rotateBitmap( int angle, Bitmap bitmap ) { + if ( bitmap == null ) { + return null; + } + + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Matrix matrix = new Matrix(); + matrix.preRotate( angle ); + return Bitmap.createBitmap( bitmap, 0, 0, width, height, matrix, true ); + } catch ( Exception e ) { + e.printStackTrace(); + return bitmap; + } + } + + /** + * Get picture's absolute path according to its uri. + * + * @param context context + * @param uri picture's uri + * @return absolute path of uri. + */ + public static String getRealPathFromUri( Context context, Uri uri ) { + int sdkVersion = Build.VERSION.SDK_INT; + if ( sdkVersion >= 19 ) { + return getRealPathFromUriAboveApi19( context, uri ); + } else { + return getRealPathFromUriBelowAPI19( context, uri ); + } + } + + /** + * Create a default {@link BitmapFactory.Options} . + * Note this options use rgb_565 and a proper inSampleSize in order to save memory. + * + * @param is data source of picture + * @param close whether to close data source + * @return options containing rgb_565 config and a proper inSampleSize. + */ + public static BitmapFactory.Options newDefaultOptions( InputStream is, boolean close ) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + options.inSampleSize = BitmapHelper.findProperInSampleSize( is, close ); + + return options; + } + + /** + * Save picture to local file. + * + * @param bitmap data source + * @param file local file to store picture. + */ + public static void savePicture( Bitmap bitmap, File file ) { + final long start = System.currentTimeMillis(); + + if ( bitmap == null || file == null ) { + Log.i( TAG, "保存失败, bitmap or file is null." ); + return; + } + if ( file.getParentFile() != null && !file.getParentFile().exists() ) { + file.getParentFile().mkdirs(); + } + + try { + final FileOutputStream fos = new FileOutputStream( file ); + bitmap.compress( Bitmap.CompressFormat.JPEG, 100, fos ); + fos.flush(); + fos.close(); + + if ( file.exists() ) { + Log.i( TAG, "保存成功" ); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + + Log.i( TAG, "saving picture costs " + ( System.currentTimeMillis() - start ) + "ms" ); + } + + /** + * 适配api19以下(不包括api19),根据uri获取图片的绝对路径 + * + * @param context 上下文对象 + * @param uri 图片的Uri + * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null + */ + private static String getRealPathFromUriBelowAPI19( Context context, Uri uri ) { + return getDataColumn( context, uri, null, null ); + } + + /** + * 适配api19及以上,根据uri获取图片的绝对路径 + * + * @param context 上下文对象 + * @param uri 图片的Uri + * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null + */ + @SuppressLint( "NewApi" ) + private static String getRealPathFromUriAboveApi19( Context context, Uri uri ) { + String filePath = null; + + try { + // 如果是document类型的 uri, 则通过document id来进行处理 + if ( DocumentsContract.isDocumentUri( context, uri ) ) { + String documentId = DocumentsContract.getDocumentId( uri ); + if ( isMediaDocument( uri ) ) { + // 使用':'分割 + String id = documentId.split( ":" )[1]; + String selection = MediaStore.Images.Media._ID + "=?"; + String[] selectionArgs = {id}; + filePath = getDataColumn( context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs ); + } else if ( isDownloadsDocument( uri ) ) { + Uri contentUri = ContentUris.withAppendedId( Uri.parse( "content://downloads/public_downloads" ), Long.valueOf( documentId ) ); + filePath = getDataColumn( context, contentUri, null, null ); + } + } else if ( "content".equalsIgnoreCase( uri.getScheme() ) ) { + filePath = getDataColumn( context, uri, null, null ); + } else if ( "file".equals( uri.getScheme() ) ) { + filePath = uri.getPath(); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + + return filePath; + } + + /** + * 获取数据库表中的 _data 列,即返回Uri对应的文件路径 + */ + private static String getDataColumn( Context context, Uri uri, String selection, String[] selectionArgs ) { + String path = null; + String[] projection = new String[]{MediaStore.Images.Media.DATA}; + Cursor cursor = null; + + try { + cursor = context.getContentResolver().query( uri, projection, selection, selectionArgs, null ); + + if ( cursor != null && cursor.moveToFirst() ) { + int columnIndex = cursor.getColumnIndexOrThrow( projection[0] ); + path = cursor.getString( columnIndex ); + } + } catch ( Exception e ) { + if ( cursor != null ) { + cursor.close(); + cursor = null; + } + } finally { + if ( cursor != null ) { + cursor.close(); + cursor = null; + } + } + + return path; + } + + /** + * @param uri the Uri to check + * @return Whether the Uri authority is MediaProvider + */ + private static boolean isMediaDocument( Uri uri ) { + return "com.android.providers.media.documents".equals( uri.getAuthority() ); + } + + /** + * @param uri the Uri to check + * @return Whether the Uri authority is DownloadsProvider + */ + private static boolean isDownloadsDocument( Uri uri ) { + return "com.android.providers.downloads.documents".equals( uri.getAuthority() ); + } + + public static int getMaxTextureSize() { + try { + // Safe minimum default size + final int IMAGE_MAX_BITMAP_DIMENSION = SIZE_DEFAULT; + + // Get EGL Display + EGL10 egl = ( EGL10 ) EGLContext.getEGL(); + EGLDisplay display = egl.eglGetDisplay( EGL10.EGL_DEFAULT_DISPLAY ); + + // Initialise + int[] version = new int[2]; + egl.eglInitialize( display, version ); + + // Query total number of configurations + int[] totalConfigurations = new int[1]; + egl.eglGetConfigs( display, null, 0, totalConfigurations ); + + // Query actual list configurations + EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]]; + egl.eglGetConfigs( display, configurationsList, totalConfigurations[0], totalConfigurations ); + + int[] textureSize = new int[1]; + int maximumTextureSize = 0; + + // Iterate through all the configurations to located the maximum texture size + for ( int i = 0; i < totalConfigurations[0]; i++ ) { + // Only need to check for width since opengl textures are always squared + egl.eglGetConfigAttrib( display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize ); + + // Keep trackCustomEvent of the maximum texture size + if ( maximumTextureSize < textureSize[0] ) + maximumTextureSize = textureSize[0]; + } + + // Release + egl.eglTerminate( display ); + + // Return largest texture size found, or default + return Math.max( maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION ); + } catch ( Exception e ) { + e.printStackTrace(); + } + + return 0; + } + + public static String bitmapToBase64( Bitmap bitmap ) { + String result = null; + try { + if ( bitmap != null ) { + result = Base64.encodeToString( bitmapToBytes( bitmap ), Base64.DEFAULT ); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + return result; + } + + public static Bitmap base64ToBitmap( String base64Data ) { + byte[] bytes = Base64.decode( base64Data, Base64.DEFAULT ); + return BitmapFactory.decodeByteArray( bytes, 0, bytes.length ); + } + + /** + * 在系统返回的intent中获取图片信息,并转化为uri + * + * @param data + * @return + */ + public static Uri convertUri( Context context, Intent data ) { + if ( data == null || data.getData() == null ) { + return null; + } + Uri localUri = data.getData(); + String scheme = localUri.getScheme(); + String imagePath = ""; + if ( "content".equals( scheme ) ) { + String[] filePathColumns = {MediaStore.Images.Media.DATA}; + Cursor c = context.getContentResolver().query( localUri, filePathColumns, null, null, null ); + if ( c != null ) { + c.moveToFirst(); + int columnIndex = c.getColumnIndex( filePathColumns[0] ); + imagePath = c.getString( columnIndex ); + c.close(); + } + } else if ( "file".equals( scheme ) ) {//小米4选择云相册中的图片是根据此方法获得路径 + imagePath = localUri.getPath(); + } + if ( TextUtils.isEmpty( imagePath ) ) { + return localUri; + } + Uri uri = Uri.fromFile( new File( imagePath ) ); + return uri != null ? uri : localUri; + } + + public static Bitmap colorToBitmap( Context context, int colorResId ) {// drawable 转换成bitmap + Bitmap.Config config = Bitmap.Config.ARGB_8888;// 取drawable的颜色格式 + Bitmap bitmap = Bitmap.createBitmap( 1, 1, config );// 建立对应bitmap + bitmap.eraseColor( context.getResources().getColor( colorResId ) ); + return bitmap; + } + + public static String getAlphaHexValue( float alpha ) { + String color = Integer.toHexString( ( int ) alpha * 255 ); + return TextUtils.isEmpty( color ) ? color : color.toUpperCase(); + } + + /** + * 抓取本地视频缩略图(操作可能耗时,尽量异步进行) + * + * @param filePath + * @return + */ + public static Bitmap getVideoThumbnail( String filePath ) { + Bitmap b = null; + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setDataSource( filePath ); + b = retriever.getFrameAtTime(); + } catch ( IllegalArgumentException e ) { + e.printStackTrace(); + } catch ( RuntimeException e ) { + e.printStackTrace(); + + } finally { + try { + retriever.release(); + } catch ( RuntimeException e ) { + e.printStackTrace(); + } + } + return b; + } + + public interface OnCompressListener { + + void onCompressSuccess( byte[] data ); + + void onCompressFailed( String msg ); + + void onBeforeCompress(); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/CheckUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/CheckUtils.java new file mode 100644 index 0000000000..39f653cf3b --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/CheckUtils.java @@ -0,0 +1,199 @@ +package com.mogo.utils; + +import android.content.Context; +import android.graphics.Rect; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CheckUtils { + + /** + * 检查任意Object是否为空 + *


+ * shallow check : 不会检查容器内部的元素是否为空 + */ + public static boolean isEmpty( Object obj) { + + if (obj == null) { + return true; + } + + if (obj instanceof Collection ) { + // 检查各种Collection是否为空(List,Queue,Set) + return (( Collection ) obj).isEmpty(); + } else if (obj instanceof Map ) { + // 检查各种Map + return (( Map ) obj).isEmpty(); + } else if (obj instanceof CharSequence ) { + // 检查各种CharSequence + return (( CharSequence ) obj).length() == 0; + } else if (obj.getClass().isArray()) { + // 检查各种base array + // return Array.getLength(obj) == 0; + if (obj instanceof Object[]) { + return (( Object[]) obj).length == 0; + } else if (obj instanceof int[]) { + return ((int[]) obj).length == 0; + } else if (obj instanceof long[]) { + return ((long[]) obj).length == 0; + } else if (obj instanceof short[]) { + return ((short[]) obj).length == 0; + } else if (obj instanceof double[]) { + return ((double[]) obj).length == 0; + } else if (obj instanceof float[]) { + return ((float[]) obj).length == 0; + } else if (obj instanceof boolean[]) { + return ((boolean[]) obj).length == 0; + } else if (obj instanceof char[]) { + return ((char[]) obj).length == 0; + } else if (obj instanceof byte[]) { + return ((byte[]) obj).length == 0; + } + } + + return false; + } + + public static boolean isExist( Object obj) { + return !isEmpty(obj); + } + + public static boolean isContainsEmpty( Object... objs) { + if (isEmpty(objs)) { + return true; + } + for ( Object obj : objs) { + if (isEmpty(obj)) { + return true; + } + } + return false; + } + + /** + * 是否为奇数 + */ + public static boolean isOdd(int i) { + return i % 2 != 0; + } + + /** + * 是否为偶数 + */ + public static boolean isEven(int i) { + return i % 2 == 0; + } + + /** + * 检查枚举组中是否包含指定枚举 + */ + public static boolean isContainsEnum( @Nullable Enum[] group, Enum child) { + + if (isEmpty(group)) { + return false; + } + + for ( Enum enums : group) { + if (enums == child) { + return true; + } + } + return false; + } + + /** + * 快速点击事件 + */ + private static long lastClickTime; + public static boolean isFastDoubleClick() { + long time = System.currentTimeMillis(); + long timeD = time - lastClickTime; + long delayTime = 500L; + if (0L < timeD && timeD < delayTime) { + return true; + } else { + lastClickTime = time; + return false; + } + } + + /** + * 网络是否可用 + */ + public static boolean isNetworkConnected( Context context) { + if ( context == null ) { + return false; + } + ConnectivityManager cm = ( ConnectivityManager ) context.getSystemService( Context.CONNECTIVITY_SERVICE); + NetworkInfo network = null; + if (cm != null) { + network = cm.getActiveNetworkInfo(); + } + return network != null && network.isAvailable() && network.isConnected(); + + } + + /** + * 检查gps开关是否已打开 + */ + public static boolean isGpsOpenStatus( Context context) { + if ( context == null ) { + return false; + } + String gps = Settings.System.getString(context.getContentResolver(), Settings.System.LOCATION_PROVIDERS_ALLOWED); + return !( TextUtils.isEmpty(gps) || !gps.contains("gps")); + } + + /** + * 检查是否模拟位置 + */ + public static boolean isOPenMockLocation( Context context) { + if ( context == null ) { + return false; + } + return Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0; + } + + /**当前点击点是否在视图中*/ + public static boolean isInsideView( MotionEvent event, View view) { + if (view != null && event != null) { + float eventX = event.getRawX(); + float eventY = event.getRawY(); + + int[] contentArray = new int[2]; + + Rect contentRect = new Rect(); + view.getLocationOnScreen(contentArray); + view.getDrawingRect(contentRect); + contentRect.offsetTo(contentArray[0], contentArray[1]); + + return contentRect.contains((int) eventX, (int) eventY); + } + + return false; + } + + /** + * 判断扫描内容是否为url格式 + */ + public static boolean isUrl( String url) { + if ( TextUtils.isEmpty(url)) { + return false; + } + String regex = "http(s)?://.*"; + Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + Matcher m = pattern.matcher(url); + return m.matches(); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/CommonUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/CommonUtils.java new file mode 100644 index 0000000000..e243da13cc --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/CommonUtils.java @@ -0,0 +1,573 @@ +package com.mogo.utils; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Enumeration; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CommonUtils { + + private static String mMacSerial = null; + private static String mCPUSerial = null; + private static boolean isMacSerialNoObtained = false; + private static boolean isCPUSerialNoObtained = false; + private static final Pattern VERSION_NAME_PATTERN = Pattern.compile( "(\\d+\\.\\d+\\.\\d+)\\-*.*" ); + + + public static String getAndroidID( Context context ) { + if ( context == null ) { + return ""; + } + return Settings.Secure.getString( context.getContentResolver(), "android_id" ); + } + + public static String getCPUSerialno() { + if ( !TextUtils.isEmpty( mCPUSerial ) ) { + return mCPUSerial; + } else if ( isCPUSerialNoObtained ) { + mCPUSerial = ""; + return mCPUSerial; + } else { + String str = ""; + + InputStreamReader ir = null; + LineNumberReader input = null; + try { + isCPUSerialNoObtained = true; + Process ex = Runtime.getRuntime().exec( "cat /proc/cpuinfo" ); + if ( ex == null ) { + return null; + } + + ir = new InputStreamReader( ex.getInputStream() ); + input = new LineNumberReader( ir ); + + while ( null != str ) { + str = input.readLine(); + if ( str != null ) { + mCPUSerial = str.trim(); + break; + } + } + } catch ( IOException var4 ) { + var4.printStackTrace(); + } finally { + if ( ir != null ) { + try { + ir.close(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + if ( input != null ) { + try { + input.close(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + } + + return mCPUSerial; + } + } + + public static int getVersionCode( Context context ) { + + if ( context == null ) { + return 1; + } + + String pkgName = context.getPackageName(); + + try { + PackageInfo e = context.getPackageManager().getPackageInfo( pkgName, 0 ); + if ( e != null ) { + return e.versionCode; + } + } catch ( Exception var2 ) { + var2.printStackTrace(); + } + + return 1; + } + + + public static String getMacSerialno() { + if ( !TextUtils.isEmpty( mMacSerial ) ) { + return mMacSerial; + } else if ( isMacSerialNoObtained ) { + mMacSerial = ""; + return mMacSerial; + } else { + String str = ""; + + InputStreamReader ir = null; + LineNumberReader input = null; + try { + isMacSerialNoObtained = true; + Process ex = Runtime.getRuntime().exec( "cat /sys/class/net/wlan0/address" ); + if ( ex == null ) { + return null; + } + + ir = new InputStreamReader( ex.getInputStream() ); + input = new LineNumberReader( ir ); + + while ( null != str ) { + str = input.readLine(); + if ( str != null ) { + mMacSerial = str.trim(); + break; + } + } + } catch ( IOException var4 ) { + var4.printStackTrace(); + } finally { + if ( ir != null ) { + try { + ir.close(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + if ( input != null ) { + try { + input.close(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + } + + return mMacSerial; + } + } + + /** + * 获取网络类型 + * + * @return + */ + public static String getNetworkType( Context context ) { + + String name = "UNKNOWN"; + + try { + + ConnectivityManager connMgr = ( ConnectivityManager ) context.getSystemService( Context.CONNECTIVITY_SERVICE ); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + if ( networkInfo != null ) { + if ( ConnectivityManager.TYPE_WIFI == networkInfo.getType() ) { + return "WIFI"; + } + } + + TelephonyManager tm = ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE ); + if ( tm == null ) { + return name; + } + + int type = tm.getNetworkType(); + switch ( type ) { + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + name = "2G"; + break; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + name = "3G"; + break; + case TelephonyManager.NETWORK_TYPE_LTE: + name = "4G"; + break; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + name = "UNKNOWN"; + break; + default: + name = "UNKNOWN"; + break; + } + } catch ( Exception e ) { + + } + return name; + } + + /** + * 得到手机的IMEI号 + * + * @return + */ + public static String getIMEI( Context context ) { + try { + TelephonyManager telephonyManager = ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE ); + if ( telephonyManager != null && + ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) == PackageManager.PERMISSION_GRANTED ) { + return telephonyManager.getDeviceId(); + } + } catch ( Exception e ) { + + } + return ""; + } + + /** + * 得到手机的IMSI号 + * + * @return + */ + public static String getIMSI( Context context ) { + + try { + TelephonyManager telephonyManager = ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE ); + if ( telephonyManager != null && + ActivityCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) == PackageManager.PERMISSION_GRANTED ) { + return telephonyManager.getSubscriberId(); + } + } catch ( Exception e ) { + + } + return ""; + } + + public static String checkSimState( Context context ) { + + String mString = ""; + + if ( context == null ) { + return mString; + } + + TelephonyManager telephonyManager = ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE ); + int simState = 0; + if ( telephonyManager != null ) { + simState = telephonyManager.getSimState(); + } + + switch ( simState ) { + + case TelephonyManager.SIM_STATE_ABSENT: + mString = "无卡"; + // do something + break; + + case TelephonyManager.SIM_STATE_NETWORK_LOCKED: + mString = "需要NetworkPIN解锁"; + // do something + + break; + + case TelephonyManager.SIM_STATE_PIN_REQUIRED: + mString = "需要PIN解锁"; + // do something + break; + + case TelephonyManager.SIM_STATE_PUK_REQUIRED: + mString = "需要PUN解锁"; + // do something + break; + + case TelephonyManager.SIM_STATE_READY: + mString = "良好"; + // do something + break; + + case TelephonyManager.SIM_STATE_UNKNOWN: + mString = "未知状态"; + // do something + break; + } + return mString; + } + + + /** + * 获取路由器Mac + */ + public static String getRouterMac( Context context ) { + + if ( context == null ) { + return ""; + } + + WifiManager wifi = ( WifiManager ) context.getApplicationContext().getSystemService( Context.WIFI_SERVICE ); + if ( wifi != null && wifi.getConnectionInfo() != null ) { + return wifi.getConnectionInfo().getBSSID(); + } + return ""; + } + + /** + * 获取wifi名字 + */ + public static String getWifiName( Context context ) { + if ( context == null ) { + return ""; + } + WifiManager wifi = ( WifiManager ) context.getApplicationContext().getSystemService( Context.WIFI_SERVICE ); + if ( wifi != null && wifi.getConnectionInfo() != null ) { + return wifi.getConnectionInfo().getSSID(); + } + return ""; + } + + + public static String getMobileIP( Context ctx ) { + + if ( ctx == null ) { + return ""; + } + + ConnectivityManager mConnectivityManager = ( ConnectivityManager ) ctx.getSystemService( Context.CONNECTIVITY_SERVICE );// 获取系统的连接服务 + // 实例化mActiveNetInfo对象 + NetworkInfo mActiveNetInfo = null;// 获取网络连接的信息 + if ( mConnectivityManager != null ) { + mActiveNetInfo = mConnectivityManager.getActiveNetworkInfo(); + } + if ( mActiveNetInfo == null ) { + return ""; + } else { + return getIp( mActiveNetInfo ); + } + } + + // 显示IP信息 + private static String getIp( NetworkInfo mActiveNetInfo ) { + + if ( mActiveNetInfo == null ) { + return ""; + } + + // 如果是WIFI网络 + if ( mActiveNetInfo.getType() == ConnectivityManager.TYPE_WIFI ) { + return getLocalIPAddress(); + } + // 如果是手机网络 + else if ( mActiveNetInfo.getType() == ConnectivityManager.TYPE_MOBILE ) { + return getLocalIPAddress(); + } else { + return ""; + } + + } + + // 获取本地IP函数 + private static String getLocalIPAddress() { + try { + Enumeration< NetworkInterface > mEnumeration = NetworkInterface.getNetworkInterfaces(); + if ( mEnumeration != null ) { + while ( mEnumeration.hasMoreElements() ) { + NetworkInterface intf = mEnumeration.nextElement(); + if ( intf != null && intf.getInetAddresses() != null ) { + Enumeration< InetAddress > enumIPAddr = intf.getInetAddresses(); + while ( enumIPAddr.hasMoreElements() ) { + InetAddress inetAddress = enumIPAddr.nextElement(); + // 如果不是回环地址 + if ( inetAddress != null && !inetAddress.isLoopbackAddress() ) { + // 直接返回本地IP地址 + return inetAddress.getHostAddress(); + } + } + } + } + } + } catch ( SocketException ex ) { + ex.printStackTrace(); + } + return ""; + } + + public static String getVersionName( Context context ) { + return getVersionName( context, true ); + } + + public static String getVersionName( Context context, boolean fullVersionName ) { + String appVersion = ""; + + try { + String packageName = context.getApplicationInfo().packageName; + appVersion = context.getPackageManager().getPackageInfo( packageName, 0 ).versionName; + if ( !fullVersionName && appVersion != null && appVersion.length() > 0 ) { + Matcher matcher = VERSION_NAME_PATTERN.matcher( appVersion ); + if ( matcher.matches() ) { + appVersion = matcher.group( 1 ); + } + } + } catch ( Exception e ) { + e.printStackTrace(); + } + + return appVersion; + } + + public static String getAppName( Context context ) { + if ( context == null ) { + return ""; + } + PackageManager pm = context.getPackageManager(); + return context.getApplicationInfo().loadLabel( pm ).toString(); + + } + + public static String getModel() { + String temp = Build.MODEL; + return TextUtils.isEmpty( temp ) ? "" : temp; + } + + @TargetApi( Build.VERSION_CODES.JELLY_BEAN ) + public static long getLeftMemory( Context context ) { + + if ( context == null ) { + return -1; + } + + if ( Build.VERSION.SDK_INT >= 16 ) { + ActivityManager mActivityManager = ( ActivityManager ) context.getSystemService( Context.ACTIVITY_SERVICE ); + ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); + mActivityManager.getMemoryInfo( mi ); + return ( mi.totalMem - mi.availMem ) / 1000; + } + return -1; + } + + + public static String encode( String string ) { + try { + return URLEncoder.encode( string, "UTF-8" ); + } catch ( UnsupportedEncodingException e ) { + e.printStackTrace(); + return ""; + } + } + + public static String decode( String string ) { + try { + return URLDecoder.decode( string, "UTF-8" ); + } catch ( UnsupportedEncodingException e ) { + e.printStackTrace(); + return ""; + } + } + + public static String getProcessName( Context context, int pid ) { + try { + ActivityManager manager = ( ActivityManager ) context.getSystemService( Context.ACTIVITY_SERVICE ); + for ( ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses() ) { + if ( processInfo.pid == pid ) { + return processInfo.processName; + } + } + } catch ( Exception e ) { + + } + return ""; + } + + public static int getStatusBarHeight( Context context ) { + + if ( context == null ) { + return 0; + } + + int statusBarHeight = 0; + try { + Class c = Class.forName( "com.android.internal.R$dimen" ); + Object obj = c.newInstance(); + Field field = c.getField( "status_bar_height" ); + int x = Integer.parseInt( field.get( obj ).toString() ); + statusBarHeight = context.getResources().getDimensionPixelSize( x ); + } catch ( Exception e ) { + } + + if ( statusBarHeight > 0 ) { + return statusBarHeight; + } + + try { + int resourceId = context.getResources().getIdentifier( "status_bar_height", "dimen", "android" ); + if ( resourceId > 0 ) { + statusBarHeight = context.getResources().getDimensionPixelSize( resourceId ); + } + } catch ( Exception e ) { + + } + + return statusBarHeight; + } + + /** + * 获取某个应用的版本名称 + * + * @param context 应用上下文 + * @param packageName 包名,如果为空,将获取 context 本身的版本名称 + * @return + */ + public static String getVersionName( @NonNull Context context, @Nullable String packageName ) { + try { + packageName = TextUtils.isEmpty( packageName ) ? context.getPackageName() : packageName; + PackageManager packageManager = context.getPackageManager(); + PackageInfo packInfo = packageManager.getPackageInfo( packageName, 0 ); + return packInfo.versionName; + } catch ( Exception e ) { + return ""; + } + } + + /** + * 获取某个应用的版本号 + * + * @param context 应用上下文 + * @param packageName 包名,如果为空,将获取 context 本身的版本号 + * @return + */ + public static int getVersionCode( @NonNull Context context, @Nullable String packageName ) { + try { + packageName = TextUtils.isEmpty( packageName ) ? context.getPackageName() : packageName; + PackageManager packageManager = context.getPackageManager(); + PackageInfo packInfo = packageManager.getPackageInfo( packageName, 0 ); + return packInfo.versionCode; + } catch ( Exception e ) { + return 0; + } + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/CountDownTimer.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/CountDownTimer.java new file mode 100644 index 0000000000..fc759cdee9 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/CountDownTimer.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; + +/** + * Schedule a countdown until a time in the future, with + * regular notifications on intervals along the way. + * + * Example of showing a 30 second countdown in a text field: + * + *
+ * new CountDownTimer(30000, 1000) {
+ *
+ *     public void onTick(long millisUntilFinished) {
+ *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
+ *     }
+ *
+ *     public void onFinish() {
+ *         mTextField.setText("done!");
+ *     }
+ *  }.start();
+ * 
+ * + * The calls to {@link #onTick(long)} are synchronized to this object so that + * one call to {@link #onTick(long)} won't ever occur before the previous + * callback is complete. This is only relevant when the implementation of + * {@link #onTick(long)} takes an amount of time to execute that is significant + * compared to the countdown interval. + */ +public abstract class CountDownTimer { + + /** + * Millis since epoch when alarm should stop. + */ + private final long mMillisInFuture; + + /** + * The interval in millis that the user receives callbacks + */ + private final long mCountdownInterval; + + private long mStopTimeInFuture; + + /** + * boolean representing if the timer was cancelled + */ + private boolean mCancelled = false; + + /** + * @param millisInFuture The number of millis in the future from the call + * to {@link #start()} until the countdown is done and {@link #onFinish()} + * is called. + * @param countDownInterval The interval along the way to receive + * {@link #onTick(long)} callbacks. + */ + public CountDownTimer(long millisInFuture, long countDownInterval) { + mMillisInFuture = millisInFuture; + mCountdownInterval = countDownInterval; + } + + /** + * Cancel the countdown. + */ + public synchronized final void cancel() { + mCancelled = true; + mHandler.removeMessages(MSG); + } + + /** + * Start the countdown. + */ + public synchronized final CountDownTimer start() { + mCancelled = false; + if (mMillisInFuture <= 0) { + onFinish(); + return this; + } + mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture; + mHandler.sendMessage(mHandler.obtainMessage(MSG)); + return this; + } + + + /** + * Callback fired on regular interval. + * @param millisUntilFinished The amount of time until finished. + */ + public abstract void onTick(long millisUntilFinished); + + /** + * Callback fired when the time is up. + */ + public abstract void onFinish(); + + + private static final int MSG = 1; + + + // handles counting down + private Handler mHandler = new Handler() { + + @Override + public void handleMessage( Message msg) { + + synchronized (CountDownTimer.this) { + if (mCancelled) { + return; + } + + final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); + + if (millisLeft <= 0) { + onFinish(); + } else if (millisLeft < mCountdownInterval) { + // no tick, just delay until done + sendMessageDelayed(obtainMessage(MSG), millisLeft); + } else { + long lastTickStart = SystemClock.elapsedRealtime(); + onTick(millisLeft); + + // take into account user's onTick taking time to execute + long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime(); + + // special case: user's onTick took more than interval to + // complete, skip to next interval + while (delay < 0) delay += mCountdownInterval; + + sendMessageDelayed(obtainMessage(MSG), delay); + } + } + } + }; +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/DateTimeUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/DateTimeUtils.java new file mode 100644 index 0000000000..ca9573894a --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/DateTimeUtils.java @@ -0,0 +1,452 @@ +package com.mogo.utils; + +import android.text.TextUtils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.regex.Pattern; + +public class DateTimeUtils { + + public static final int DATETIME_FIELD_REFERSH = 10; // 刷新时间(分钟), + // + public static final long ONE_SECOND = 1000L; + public static final long ONE_MINUTE = ONE_SECOND * 60L; + public static final long ONE_HOUR = ONE_MINUTE * 60L; + public static final long ONE_DAY = ONE_HOUR * 24L; + public static final long ONE_MONTH = ONE_DAY * 24L; + public static final long ONE_YEAR = ONE_MONTH * 24L; + // 下面的pattern在print和parse时都可以使用 + public static final String MM_Yue_dd_Ri = "MM月dd日"; + public static final String MM_Yue_dd_Ri_HH_mm = "MM月dd日 HH:mm"; + public static final String M_Yue_d_Ri = "M月d日"; + public static final String d_Ri = "d日"; + public static final String yyyyMMdd = "yyyyMMdd"; + public static final String yyyy_MM_dd = "yyyy-MM-dd"; + public static final String yyyy_MM = "yyyy-MM"; + public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; + public static final String yyyy_MM_dd_HH_mm = "yyyy-MM-dd HH:mm"; + public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss"; + public static final String HH_mm = "HH:mm"; + public static final String yyyy_Nian_MM_Yue_dd_Ri = "yyyy年MM月dd日"; + public static final String yyyy_Nian_MM_Yue = "yyyy年MM月"; + public static final String MM_yy = "MM/yy"; + public static final String dd_MM = "dd/MM"; + public static final String MM_dd = "MM-dd"; + private static final String pattern2 = "MM 月 dd"; + // 下面的pattern是print时用,parse时不应使用(只有时间,没有日期) + public static final String HH_mm_ss = "HH:mm:ss"; + private static final String[] PATTERNS = {yyyy_MM_dd_HH_mm_ss, yyyy_MM_dd_HH_mm, yyyy_MM_dd, yyyyMMdd}; + + + public static Calendar cleanCalendarTime( Calendar c) { + c.set( Calendar.HOUR_OF_DAY, 0); + c.set( Calendar.MINUTE, 0); + c.set( Calendar.SECOND, 0); + c.set( Calendar.MILLISECOND, 0); + return c; + } + + /** + * 获得指定日期表示格式转换成Calendar的格式 + * + * @param src + * @param fallback 若无法转换,返回一个默认值 + * @return + */ + public static Calendar getCalendar( T src, Calendar fallback) { + if (src != null) { + try { + return getCalendar(src); + } catch ( Exception e) { + } + } + return ( Calendar ) fallback.clone(); + } + + /** + * 获得日期类型 + * + * @param src 任何可以表示时间的类型,目前支持Calendar,Date,long,String + * @return Calendar类型表示的时间 + * @throws IllegalArgumentException + */ + public static Calendar getCalendar( T src) { + Calendar calendar = Calendar.getInstance(); + calendar.setLenient(false); + if (src == null) { + return null; + } else if (src instanceof Calendar ) { + calendar.setTimeInMillis((( Calendar ) src).getTimeInMillis()); + } else if (src instanceof Date ) { + calendar.setTime(( Date ) src); + } else if (src instanceof Long ) { + calendar.setTimeInMillis(( Long ) src); + } else if (src instanceof String ) { + String nSrc = ( String ) src; + if ( TextUtils.isEmpty(nSrc)) { + return null; + } + try { + // 直接匹配的时候不能匹配到月份或日期不是2位数的情况 + if ( Pattern.compile("\\d{4}年\\d{1,2}月\\d{1,2}日").matcher(nSrc).find()) { + nSrc = fixDateString(nSrc); + return getCalendarByPattern(nSrc, yyyy_MM_dd); + } + return getCalendarByPatterns(nSrc, PATTERNS); + } catch ( Exception e) { + try { + calendar.setTimeInMillis( Long.valueOf(nSrc)); + } catch ( NumberFormatException e1) { + throw new IllegalArgumentException(e1); + } + } + } else { + throw new IllegalArgumentException(); + } + return calendar; + } + + /** + * YYYY年MM月DD日 --> YYYY-MM-DD + */ + private static String fixDateString( String date) { + if ( TextUtils.isEmpty(date)) { + return date; + } + + String[] dateArray = date.split("[年月日]"); + if (dateArray.length == 1) { + dateArray = date.split("-"); + } + for (int i = 0; i < 3; i++) { + if (dateArray[i].length() == 1) { + dateArray[i] = "0" + dateArray[i]; + } + } + return dateArray[0] + "-" + dateArray[1] + "-" + dateArray[2]; + } + + /** + * 匹配pattern获得时间,若无法解析抛出异常 + * + * @param dateTimeStr + * @param patternStr + * @return + * @throws IllegalArgumentException + */ + public static Calendar getCalendarByPattern( String dateTimeStr, String patternStr) { + try { + SimpleDateFormat sdf = new SimpleDateFormat(patternStr, Locale.US); + sdf.setLenient(false); + Date d = sdf.parse(dateTimeStr); + Calendar c = Calendar.getInstance(); + c.setLenient(false); + c.setTimeInMillis(d.getTime()); + return c; + } catch ( Exception e) { + throw new IllegalArgumentException(e); + } + } + + /** + * 匹配pattern数组中的所有pattern解析时间格式,若没有可以解析的方式则抛出异常 + * + * @param dateTimeStr + * @param patternStr + * @return + * @throws IllegalArgumentException + */ + public static Calendar getCalendarByPatterns( String dateTimeStr, String[] patternStr) { + for ( String string : patternStr) { + try { + return getCalendarByPattern(dateTimeStr, string); + } catch ( Exception e) { + } + } + + throw new IllegalArgumentException(); + } + + /** + * 是否有服务器时间 + */ + public static boolean hasServerTime; + /** + * 本地时间和服务器时间的间隔 time server local gap millis + */ + public static long tslgapm; + /** + * 本地时间和服务器时间的间隔 time server string + */ + public static String tss; + + /** + * 获取与服务器时间矫正过的当前时间 + */ + public static Calendar getCurrentDateTime() { + Calendar now = Calendar.getInstance(); + now.setLenient(false); + if (hasServerTime) { + now.setTimeInMillis(now.getTimeInMillis() + tslgapm); + } + return now; + } + + public static Calendar getCurrentDate() { + return cleanCalendarTime(getCurrentDateTime()); + } + + public static long getCurTimeInMillis(){ + return System.currentTimeMillis(); + } + + /** + * login时server的日期 + * + * @return + */ + public static Calendar getLoginServerDate() { + return getCalendar(tss); + } + + /** + * 获得基准日期增加间隔天 + */ + public static Calendar getDateAdd( Calendar start, int interval) { + if (start == null) { + return null; + } + Calendar c = ( Calendar ) start.clone(); + c.add( Calendar.DATE, interval); + return c; + } + + /** + * 获得时间间隔 + * + * @param from + * @param to + * @param unit 时间间隔单位{@link DateTimeUtils#ONE_SECOND},{@link DateTimeUtils#ONE_MINUTE}, + * {@link DateTimeUtils#ONE_HOUR}, {@link DateTimeUtils#ONE_DAY} + * @return + */ + public static long getIntervalTimes( Calendar from, Calendar to, long unit) { + if (from == null || to == null) { + return 0; + } + return Math.abs(from.getTimeInMillis() - to.getTimeInMillis()) / unit; + } + + /** + * 获得日期间隔 忽略小时 + * + * @param startdate + * @param enddate + * @return + */ + + public static int getIntervalDays( String startdate, String enddate, String pattern) { + int betweenDays = 0; + if (startdate == null || enddate == null) { + return betweenDays; + } + + Calendar d1 = getCalendarByPattern(startdate, pattern); + Calendar d2 = getCalendarByPattern(enddate, pattern); + + return getIntervalDays(d1, d2); + } + + public static int getIntervalDays(T from, T to) { + Calendar startdate = getCalendar(from); + Calendar enddate = getCalendar(to); + cleanCalendarTime(startdate); + cleanCalendarTime(enddate); + return (int) getIntervalTimes(startdate, enddate, ONE_DAY); + } + + private static String[] weekdays = {"", "周日", "周一", "周二", "周三", "周四", "周五", "周六",}; + private static String[] weekdays1 = {"", "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六",}; + + /** + * calendar --> 周一~周日 + */ + public static String getWeekDayFromCalendar( Calendar cal) { + if (cal == null) { + throw new IllegalArgumentException(); + } + return weekdays[cal.get( Calendar.DAY_OF_WEEK)]; + } + + /** + * calendar --> 星期日~星期六 + */ + public static String getWeekDayFromCalendar1( Calendar cal) { + if (cal == null) { + throw new IllegalArgumentException(); + } + return weekdays1[cal.get( Calendar.DAY_OF_WEEK)]; + } + + /** + * 判断是否是闰年 这个方法不要改动! + * + * @param date(2009-10-13 || 2009年10月13日 || 2009) + * @return true 是 false 不是 + */ + public static boolean isLeapyear( String date) { + Calendar calendar = getCalendar(date); + if (calendar != null) { + int year = calendar.get( Calendar.YEAR); + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + return false; + } + + // 是否到刷新时间 + public static boolean isRefersh(long beforeTime) { + return isRefersh(DATETIME_FIELD_REFERSH * 1000 * 60, beforeTime); + } + + // 是否到刷新时间 + public static boolean isRefersh(long gap, long beforeTime) { + return new Date().getTime() - beforeTime >= gap; + } + + public static String printCalendarByPattern( Calendar c, String patternStr) { + if (null == c || null == patternStr) { + return null; + } + SimpleDateFormat sdf = new SimpleDateFormat(patternStr, Locale.US); + sdf.setLenient(false); + return sdf.format(c.getTime()); + } + + /** + * 只通过年月日比较两个Calendar + * + * @return c1 < c2 = -1 ; c1 > c2 = 1 ; c1 == c2 = 0 + */ + public static int compareCalendarIgnoreTime( Calendar c1, Calendar c2) { + if (c1.get( Calendar.YEAR) > c2.get( Calendar.YEAR)) { + return 1; + } else if (c1.get( Calendar.YEAR) < c2.get( Calendar.YEAR)) { + return -1; + } else { + if (c1.get( Calendar.MONTH) > c2.get( Calendar.MONTH)) { + return 1; + } else if (c1.get( Calendar.MONTH) < c2.get( Calendar.MONTH)) { + return -1; + } else { + if (c1.get( Calendar.DAY_OF_MONTH) > c2.get( Calendar.DAY_OF_MONTH)) { + return 1; + } else if (c1.get( Calendar.DAY_OF_MONTH) < c2.get( Calendar.DAY_OF_MONTH)) { + return -1; + } else { + return 0; + } + } + } + } + + public static void setTimeWithHHmm( Calendar src, String HH_mm) { + if ( TextUtils.isEmpty(HH_mm) || null == src) { + return; + } + String s[] = HH_mm.split(":"); + if (s.length != 2) { + return; + } + try { + cleanCalendarTime(src); + src.set( Calendar.HOUR_OF_DAY, Integer.valueOf(s[0])); + src.set( Calendar.MINUTE, Integer.valueOf(s[1])); + } catch ( NumberFormatException e) { + } + + } + + public static int getDayDiff(long time1, long time2) { + Date dateA = new Date(time1); + Date dateB = new Date(time2); + Calendar calDateA = Calendar.getInstance(); + calDateA.setTime(dateA); + Calendar calDateB = Calendar.getInstance(); + calDateB.setTime(dateB); + + if (calDateA.get( Calendar.YEAR) == calDateB.get( Calendar.YEAR) + && calDateA.get( Calendar.MONTH) == calDateB.get( Calendar.MONTH)) { + return calDateB.get( Calendar.DAY_OF_MONTH) - calDateA.get( Calendar.DAY_OF_MONTH); + } else if (calDateA.get( Calendar.YEAR) == calDateB.get( Calendar.YEAR) && ((calDateB.get( Calendar.MONTH) - calDateA.get( Calendar.MONTH)) == 1 + || (calDateB.get( Calendar.MONTH) - calDateA.get( Calendar.MONTH)) == -11)) {//处理跨年情况 + return calDateB.get( Calendar.DAY_OF_MONTH) + (getCurrentMonthLastDay() - calDateA.get( Calendar.DAY_OF_MONTH)); + } + return 0; + } + + public static int getCurrentMonthLastDay() { + Calendar a = Calendar.getInstance(); + a.set( Calendar.DATE, 1); + a.roll( Calendar.DATE, -1); + int maxDate = a.get( Calendar.DATE); + return maxDate; + } + + public static String convertToChineseWeekNumber( int number) { + switch (number) { + case 1: + return "一"; + case 2: + return "二"; + case 3: + return "三"; + case 4: + return "四"; + case 5: + return "五"; + case 6: + return "六"; + case 0: + return "日"; + default: + return ""; + } + } + + /** + * 获取分钟数 + */ + private static int getMinutes( String str, int blankCount) { + int ret = 0; + if (str.split(" ").length < (blankCount + 1)) { + return ret; + } + String hh_mm = str.split(" ")[blankCount]; + + String s = ""; + if (!TextUtils.isEmpty(hh_mm) && hh_mm.length() >= 4) { + s = hh_mm.substring(3); + } + try { + ret = Integer.parseInt(s); + } catch ( NumberFormatException e) { + e.printStackTrace(); + } + return ret; + } + + /** + * 获取 06月07 格式的日期 + * @param timestamp 时间戳 + * @return + */ + public static String getTimeText( long timestamp) { + SimpleDateFormat format = new SimpleDateFormat(yyyy_Nian_MM_Yue_dd_Ri, Locale.US); + String strStart = format.format(new Date(timestamp)); + return strStart; + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/DeviceIdUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/DeviceIdUtils.java new file mode 100644 index 0000000000..3e561c9530 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/DeviceIdUtils.java @@ -0,0 +1,118 @@ +package com.mogo.utils; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import androidx.core.content.ContextCompat; + + +import com.mogo.utils.storage.SharedPrefsMgr; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class DeviceIdUtils { + + public static final String KEY_DEVICE_ID = "deviceId"; + + private DeviceIdUtils() {} + + private static void saveDeviceId( Context context, String deviceId){ + SharedPrefsMgr.getInstance(context).putString(KEY_DEVICE_ID, deviceId); + } + + public static String getDeviceId( Context context) { + if(context == null){ + throw new NullPointerException("context must not be null."); + } + + final Context appContext = context.getApplicationContext(); + String deviceId = SharedPrefsMgr.getInstance( context ).getString( KEY_DEVICE_ID ); + + if ( TextUtils.isEmpty( deviceId )) { + deviceId = getDeviceIdInternal(appContext); + if (!TextUtils.isEmpty(deviceId)) { + saveDeviceId(appContext,deviceId); + } else { + if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) { + if ( ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE ) == PackageManager.PERMISSION_GRANTED ) { + deviceId = (( TelephonyManager ) appContext.getSystemService( Context.TELEPHONY_SERVICE)).getSimSerialNumber(); + } + }else{ + deviceId = (( TelephonyManager ) appContext.getSystemService( Context.TELEPHONY_SERVICE)).getSimSerialNumber(); + } + if (!TextUtils.isEmpty(deviceId)) { + saveDeviceId(appContext,deviceId); + } else { + deviceId = getDeviceSerial(); + if (!TextUtils.isEmpty(deviceId) && !deviceId.equalsIgnoreCase("unknown")) { + saveDeviceId(appContext,deviceId); + } else { + deviceId = getAndroidId(appContext); + if (!TextUtils.isEmpty(deviceId)) { + saveDeviceId(appContext,deviceId); + } else { + deviceId = String.valueOf( System.currentTimeMillis()); + saveDeviceId(appContext,deviceId); + } + } + } + } + } + + return deviceId; + } + + private static String getDeviceIdInternal( Context context) { + String id = ""; + + if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) { + if ( ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { + return id; + } + } + + TelephonyManager telephonymanager = ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE); + if (telephonymanager != null) { + id = telephonymanager.getDeviceId(); + if ( TextUtils.isEmpty(id)) + id = ""; + } + return id; + } + + private static String getAndroidId( Context context) { + String s = ""; + s = Settings.Secure.getString(context.getContentResolver(), "android_id"); + if ( TextUtils.isEmpty(s)) + s = ""; + return s; + } + + private static String getDeviceSerial() { + String serial = "unknown"; + try { + Class clazz = Class.forName("android.os.Build"); + Class paraTypes = Class.forName("java.lang.String"); + Method method = clazz.getDeclaredMethod("getString", paraTypes); + if (!method.isAccessible()) { + method.setAccessible(true); + } + serial = ( String ) method.invoke(new Build(), "ro.serialno"); + } catch ( ClassNotFoundException e) { + e.printStackTrace(); + } catch ( NoSuchMethodException e) { + e.printStackTrace(); + } catch ( InvocationTargetException e) { + e.printStackTrace(); + } catch ( IllegalAccessException e) { + e.printStackTrace(); + } + return serial; + } +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/FileUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/FileUtils.java new file mode 100644 index 0000000000..e7067de16b --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/FileUtils.java @@ -0,0 +1,250 @@ +package com.mogo.utils; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Base64; + +import androidx.core.content.FileProvider; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; + + +public class FileUtils { + public static boolean createFileDir( String fileDir ) { + + if ( TextUtils.isEmpty( fileDir ) ) { + return false; + } + + try { + File dir = new File( fileDir ); + return dir.exists() || dir.mkdir(); + } catch ( Exception e ) { + return false; + } + } + + public static boolean createFileDir( File dir ) { + if ( dir == null ) { + return false; + } + try { + return dir.exists() || dir.mkdir(); + } catch ( Exception e ) { + return false; + } + } + + public static void writeToFile( String fileDir, String fileName, String content ) { + if ( fileDir == null || fileName == null || content == null ) { + return; + } + if ( !createFileDir( fileDir ) ) { + return; + } + FileOutputStream fos = null; + OutputStreamWriter osw = null; + try { + fos = new FileOutputStream( fileDir + fileName, true ); + osw = new OutputStreamWriter( fos ); + osw.write( content ); + osw.flush(); + } catch ( IOException e ) { + e.printStackTrace(); + } finally { + IOUtils.closeSilently( fos ); + IOUtils.closeSilently( osw ); + } + } + + /** + * Read a text file into a String, optionally limiting the length. + */ + public static String readTextFile( File file ) { + InputStream is = null; + BufferedInputStream bis = null; + ByteArrayOutputStream bos = null; + String text = null; + try { + is = new FileInputStream( file ); + bis = new BufferedInputStream( is ); + bos = new ByteArrayOutputStream(); + int len; + byte[] data = new byte[1024]; + do { + len = bis.read( data ); + if ( len > 0 ) bos.write( data, 0, len ); + } while ( len == data.length ); + text = bos.toString(); + } catch ( Exception e ) { + e.printStackTrace(); + } finally { + IOUtils.closeSilently( is ); + IOUtils.closeSilently( bis ); + IOUtils.closeSilently( bos ); + } + return text; + } + + public static String fileToBase64( File file ) { + String base64 = null; + InputStream in = null; + try { + in = new FileInputStream( file ); + byte[] bytes = new byte[in.available()]; + int length = in.read( bytes ); + base64 = Base64.encodeToString( bytes, 0, length, Base64.DEFAULT ); + } catch ( FileNotFoundException e ) { + e.printStackTrace(); + } catch ( IOException e ) { + e.printStackTrace(); + } finally { + IOUtils.closeSilently( in ); + } + return base64; + } + + /** + * Writes string to file. Basically same as "echo -n $string > $filename" + */ + public static void stringToFile( String filename, String string ) { + FileWriter out = null; + try { + out = new FileWriter( filename ); + out.write( string ); + } catch ( Exception e ) { + e.printStackTrace(); + } finally { + IOUtils.closeSilently( out ); + } + } + + public static InputStream stringToStream( String content ) { + InputStream inputStream = null; + try { + inputStream = new ByteArrayInputStream( content.getBytes() ); + } catch ( Exception e ) { + e.printStackTrace(); + } + return inputStream; + } + + public static String streamToString( InputStream is ) throws IOException { + String content = null; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int i = -1; + while ( ( i = is.read() ) != -1 ) { + bos.write( i ); + } + content = bos.toString(); + } catch ( Exception e ) { + e.printStackTrace(); + } + return content; + } + + public static String getStringFromFile( Context context, String fileName ) { + FileInputStream fis = null; + ByteArrayOutputStream os = null; + String content = null; + try { + fis = context.openFileInput( fileName ); + os = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = -1; + while ( ( length = fis.read( buffer ) ) != -1 ) { + os.write( buffer, 0, length ); + } + content = os.toString(); + } catch ( Exception e ) { + e.printStackTrace(); + } finally { + IOUtils.closeSilently( fis ); + IOUtils.closeSilently( os ); + } + return content; + } + + public static InputStream getStreamFromFile( Context context, String fileName ) { + FileInputStream fis = null; + try { + fis = context.openFileInput( fileName ); + } catch ( Exception e ) { + e.printStackTrace(); + } + return fis; + } + + public static void saveStringToFile( Context context, String content, String fileName ) { + try { + FileOutputStream fos = context.openFileOutput( fileName, Context.MODE_PRIVATE ); + fos.write( content.getBytes() ); + IOUtils.closeSilently( fos ); + } catch ( Exception e ) { + e.printStackTrace(); + } + } + + /** + * 将scheme为file的uri转成FileProvider 提供的content uri + */ + public static Uri convertFileUriToFileProviderUri( Context context, Uri uri ) { + if ( uri == null ) return null; + if ( ContentResolver.SCHEME_FILE.equals( uri.getScheme() ) ) { + return getUriForFile( context, new File( uri.getPath() ) ); + } + return uri; + + } + + /** + * 创建一个用于拍照图片输出路径的Uri (FileProvider) + */ + public static Uri getUriForFile( Context context, File file ) { + return FileProvider.getUriForFile( context, getFileProviderName( context ), file ); + } + + public static String getFileProviderName( Context context ) { + return context.getPackageName() + ".fileprovider"; + } + + /** + * 把Uri 解析出文件绝对路径 + */ + public static String parseOwnUri( Context context, Uri uri ) { + if ( uri == null ) return null; + String path; + if ( TextUtils.equals( uri.getAuthority(), getFileProviderName( context ) ) ) { + path = new File( uri.getPath() ).getAbsolutePath(); + } else { + path = uri.getPath(); + } + return path; + } + + public static String getFileStreamPath( Context context, String name ) { + String absFileName = null; + try { + File file = context.getFileStreamPath( name ); + if ( file != null && file.exists() ) { + absFileName = file.getAbsolutePath(); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + return absFileName; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/IOUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/IOUtils.java new file mode 100644 index 0000000000..26c7936c2b --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/IOUtils.java @@ -0,0 +1,49 @@ +package com.mogo.utils; + + +import androidx.annotation.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.InputStream; + +public class IOUtils { + + public static byte[] inputToBytes( InputStream is) { + if(is == null){ + return null; + } + + ByteArrayOutputStream bos = null; + byte[] result = null; + + try{ + bos = new ByteArrayOutputStream(); + byte[] buff = new byte[100]; + int rc = 0; + while ((rc = is.read(buff, 0, 100)) > 0) { + bos.write(buff, 0, rc); + } + + result = bos.toByteArray(); + }catch ( Exception e){ + e.printStackTrace(); + result = null; + }finally { + closeSilently(bos); + } + + return result; + } + + public static void closeSilently(@Nullable Closeable c) { + if (c == null) return; + try { + c.close(); + c = null; + } catch ( Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/IntentUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/IntentUtils.java new file mode 100644 index 0000000000..f36c5d3557 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/IntentUtils.java @@ -0,0 +1,361 @@ +package com.mogo.utils; + +import android.net.Uri; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.TreeMap; + +public class IntentUtils { + + /** + * urlDecode的时候会把'+'转换成' ' + * + * @param uri + * @return + */ + public static TreeMap< String, String > splitParams2( Uri uri) { + if (uri == null) { + return new TreeMap< String, String >(); + } + Set< String > keys = getQueryParameterNames(uri); + TreeMap< String, String > map = new TreeMap< String, String >(); + + for ( String key : keys) { + map.put(key, getQueryParameter(uri, key)); + } + return map; + } + + /** + * urlDecode时
+ * 4.0版本以后 '+' ->' '
+ * 2.3版本之前 '+' -> '+'
+ * + * @param uri + * @return + */ + public static HashMap< String, String > splitParams1( Uri uri) { + if (uri == null) { + return new HashMap< String, String >(); + } + Set< String > keys = getQueryParameterNames(uri); + HashMap< String, String > map = new HashMap< String, String >(keys.size()); + + for ( String key : keys) { + map.put(key, uri.getQueryParameter(key)); + } + return map; + } + + /** + * Searches the query string for the first value with the given key. + * + *

+ * Warning: this decoded the '+' character as ' '. + * + * @param key which will be encoded + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * @throws NullPointerException if key is null + * @return the decoded value or null if no parameter is found + */ + public static String getQueryParameter( Uri uri, String key) { + if (uri.isOpaque()) { + throw new UnsupportedOperationException("This isn't a hierarchical URI."); + } + if (key == null) { + throw new NullPointerException("key"); + } + + final String query = uri.getEncodedQuery(); + if (query == null) { + return null; + } + + final String encodedKey = Uri.encode(key, null); + final int length = query.length(); + int start = 0; + do { + int nextAmpersand = query.indexOf('&', start); + int end = nextAmpersand != -1 ? nextAmpersand : length; + + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + + if (separator - start == encodedKey.length() + && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { + if (separator == end) { + return ""; + } else { + String encodedValue = query.substring(separator + 1, end); + return UriCodec.decode(encodedValue, true, UriCodec.UTF_8, false); + } + } + + // Move start to end of name. + if (nextAmpersand != -1) { + start = nextAmpersand + 1; + } else { + break; + } + } while (true); + return null; + } + + public static Set< String > getQueryParameterNames( Uri uri) { + if (uri.isOpaque()) { + throw new UnsupportedOperationException("This isn't a hierarchical URI."); + } + + String query = uri.getEncodedQuery(); + if (query == null) { + return Collections.emptySet(); + } + + Set< String > names = new LinkedHashSet< String >(); + int start = 0; + do { + int next = query.indexOf('&', start); + int end = next == -1 ? query.length() : next; + + int separator = query.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + + String name = query.substring(start, separator); + names.add( Uri.decode(name)); + + // Move start to end of name. + start = end + 1; + } while (start < query.length()); + + return Collections.unmodifiableSet(names); + } + + /** + * Encodes and decodes {@code application/x-www-form-urlencoded} content. Subclasses define exactly which characters + * are legal. + * + *

+ * By default, UTF-8 is used to encode escaped characters. A single input character like "\u0080" may be encoded to + * multiple octets like %C2%80. + */ + abstract static class UriCodec { + + /** + * Returns true if {@code c} does not need to be escaped. + */ + protected abstract boolean isRetained(char c); + + /** + * Throws if {@code s} is invalid according to this encoder. + */ + public final String validate( String uri, int start, int end, String name) throws URISyntaxException { + for (int i = start; i < end;) { + char ch = uri.charAt(i); + if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || isRetained(ch)) { + i++; + } else if (ch == '%') { + if (i + 2 >= end) { + throw new URISyntaxException(uri, "Incomplete % sequence in " + name, i); + } + int d1 = hexToInt(uri.charAt(i + 1)); + int d2 = hexToInt(uri.charAt(i + 2)); + if (d1 == -1 || d2 == -1) { + throw new URISyntaxException(uri, "Invalid % sequence: " + uri.substring(i, i + 3) + " in " + + name, i); + } + i += 3; + } else { + throw new URISyntaxException(uri, "Illegal character in " + name, i); + } + } + return uri.substring(start, end); + } + + /** + * Throws if {@code s} contains characters that are not letters, digits or in {@code legal}. + */ + public static void validateSimple( String s, String legal) throws URISyntaxException { + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (!(ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || legal.indexOf(ch) > -1)) { + throw new URISyntaxException(s, "Illegal character", i); + } + } + } + + /** + * Encodes {@code s} and appends the orderList to {@code builder}. + * + * @param isPartiallyEncoded true to fix input that has already been partially or fully encoded. For example, + * input of "hello%20world" is unchanged with isPartiallyEncoded=true but would be double-escaped to + * "hello%2520world" otherwise. + * @throws UnsupportedEncodingException + */ + private void appendEncoded( StringBuilder builder, String s, Charset charset, boolean isPartiallyEncoded) + throws UnsupportedEncodingException { + if (s == null) { + throw new NullPointerException(); + } + + int escapeStart = -1; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || isRetained(c) || c == '%' + && isPartiallyEncoded) { + if (escapeStart != -1) { + appendHex(builder, s.substring(escapeStart, i), charset); + escapeStart = -1; + } + if (c == '%' && isPartiallyEncoded) { + // this is an encoded 3-character sequence like "%20" + builder.append(s, i, i + 3); + i += 2; + } else if (c == ' ') { + builder.append('+'); + } else { + builder.append(c); + } + } else if (escapeStart == -1) { + escapeStart = i; + } + } + if (escapeStart != -1) { + appendHex(builder, s.substring(escapeStart, s.length()), charset); + } + } + + public final String encode( String s, Charset charset) throws UnsupportedEncodingException { + // Guess a bit larger for encoded form + StringBuilder builder = new StringBuilder(s.length() + 16); + appendEncoded(builder, s, charset, false); + return builder.toString(); + } + + static final Charset UTF_8 = Charset.forName("UTF-8"); + + public final void appendEncoded( StringBuilder builder, String s) throws UnsupportedEncodingException { + appendEncoded(builder, s, UTF_8, false); + } + + public final void appendPartiallyEncoded( StringBuilder builder, String s) throws UnsupportedEncodingException { + appendEncoded(builder, s, UTF_8, true); + } + + /** + * @param convertPlus true to convert '+' to ' '. + * @param throwOnFailure true to throw an IllegalArgumentException on invalid escape sequences; false to replace + * them with the replacement character (U+fffd). + * @throws UnsupportedEncodingException + */ + public static String decode( String s, boolean convertPlus, Charset charset, boolean throwOnFailure) { + if (s.indexOf('%') == -1 && (!convertPlus || s.indexOf('+') == -1)) { + return s; + } + + StringBuilder result = new StringBuilder(s.length()); + ByteArrayOutputStream out = new ByteArrayOutputStream();; + try { + for (int i = 0; i < s.length();) { + char c = s.charAt(i); + if (c == '%') { + do { + int d1, d2; + if (i + 2 < s.length() && (d1 = hexToInt(s.charAt(i + 1))) != -1 + && (d2 = hexToInt(s.charAt(i + 2))) != -1) { + out.write((byte) ((d1 << 4) + d2)); + } else if (throwOnFailure) { + throw new IllegalArgumentException("Invalid % sequence at " + i + ": " + s); + } else { + byte[] replacement = "\ufffd".getBytes(charset.name()); + out.write(replacement, 0, replacement.length); + } + i += 3; + } while (i < s.length() && s.charAt(i) == '%'); + result.append(new String(out.toByteArray(), charset.name())); + out.reset(); + } else { + if (convertPlus && c == '+') { + c = ' '; + } + result.append(c); + i++; + } + } + } catch ( UnsupportedEncodingException e) { + Log.e("IntentUtils",e.getMessage()); + }finally { + try { + out.close(); + } catch ( IOException e) { + Log.e("IntentUtils",e.getMessage()); + } + } + return result.toString(); + } + + /** + * Like {@link Character#digit}, but without support for non-ASCII characters. + */ + private static int hexToInt(char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } else if ('a' <= c && c <= 'f') { + return 10 + c - 'a'; + } else if ('A' <= c && c <= 'F') { + return 10 + c - 'A'; + } else { + return -1; + } + } + + public static String decode( String s) throws UnsupportedEncodingException { + return decode(s, false, UTF_8, true); + } + + private static void appendHex( StringBuilder builder, String s, Charset charset) + throws UnsupportedEncodingException { + for (byte b : s.getBytes(charset.name())) { + appendHex(builder, b); + } + } + + private static void appendHex( StringBuilder sb, byte b) { + sb.append('%'); + sb.append(byteToHexString(b, true)); + } + + /** + * The digits for every supported radix. + */ + private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z' }; + + private static final char[] UPPER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z' }; + + public static String byteToHexString( byte b, boolean upperCase) { + char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS; + char[] buf = new char[2]; // We always want two digits. + buf[0] = digits[b >> 4 & 0xf]; + buf[1] = digits[b & 0xf]; + return new String(buf, 0, 2); + } + + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/MapUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/MapUtils.java new file mode 100644 index 0000000000..e2deae92c4 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/MapUtils.java @@ -0,0 +1,25 @@ +package com.mogo.utils; + +import java.util.Map; +import java.util.Set; + +/** + * Created by congtaowang on 2018/11/20. + */ +public class MapUtils { + + public static void putNotAllowNull( final Map< String, Object > target, final String key, final Object value ) { + if ( target != null && key != null && value != null ) { + target.put( key, value ); + } + } + + public static void putAllNotAllowNull( final Map< String, Object > target, final Map< String, Object > source ) { + if ( target != null && source != null && !source.isEmpty() ) { + final Set< String > keys = source.keySet(); + for ( String key : keys ) { + putNotAllowNull( target, key, source.get( key ) ); + } + } + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/MatcherUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/MatcherUtils.java new file mode 100644 index 0000000000..25c5bf568a --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/MatcherUtils.java @@ -0,0 +1,398 @@ +/* + * 创建日期:2012-10-9 + */ +package com.mogo.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MatcherUtils { + /** + * 匹配:Email地址 + */ + public static final String REGEX_EMAIL = "^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"; + /** + * 匹配:手机号码 + */ + public static final String REGEX_MOBILE_NUMBER = "^[1][3-9]\\d{9}$"; + + /** + * 正则表达式中使用的特殊的计算符号,如:'$', '*', ... + */ + public static final char[] PATTERN_REGEX_SPECIAL_CHARACTERS = { '$', // 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,请使用 \$。 + '(', ')', // 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。 + '*', // 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。 + '+', // 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。 + '.', // 匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \。 + '[', // 标记一个中括号表达式的开始。要匹配 [,请使用 \[。 + '?', // 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。 + '\\', // 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。 + '^', // 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。 + '{', // 标记限定符表达式的开始。要匹配 {,请使用 \{。 + '|', // 指明两项之间的一个选择。要匹配 |,请使用 \|。 + }; + + /** 英文标点符号 */ + private static final int TYPE_PUNCT = 1; + /** 数字 */ + private static final int TYPE_DIGIT = 2; + /** 小写字母字符 */ + private static final int TYPE_LOWER_LETTER = 3; + /** 大写字母字符 */ + private static final int TYPE_UPPER_LETTER = 4; + /** 汉字字符串包括了中文标点符号 */ + private static final int TYPE_CJK = 5; + /** 汉字字符串剔除了中文标点符号 */ + private static final int TYPE_NO_PUNCTUATION_CJK = 6; + /** 是否是空白字符 */ + private static final int TYPE_WHITESPACE = 7; + /** 不是字母 数字 英文标点符号 */ + private static final int TYPE_NOT_PUNCT_DIGIT_LETTER = 8; + + private static boolean isMatchesChar(int type, char ch) { + switch (type) { + case TYPE_PUNCT: + return isPunct(ch); + case TYPE_DIGIT: + return Character.isDigit(ch); + case TYPE_LOWER_LETTER: + return isLowerLetter(ch); + case TYPE_UPPER_LETTER: + return isUpperLetter(ch); + case TYPE_CJK: + return isCJK(ch); + case TYPE_NO_PUNCTUATION_CJK: + return isNoPunctuationCJK(ch); + case TYPE_WHITESPACE: + return Character.isWhitespace(ch); + case TYPE_NOT_PUNCT_DIGIT_LETTER: + return !( Character.isDigit(ch) || isLowerLetter(ch) || isUpperLetter(ch) || isPunct(ch)); + default: + return false; + } + } + + private static boolean isMatches(int type, String input) { + if (input != null && input.length() > 0) { + for (int i = input.length() - 1; i >= 0; i--) { + if (!isMatchesChar(type, input.charAt(i))) { + return false; + } else if (i == 0) { + return true; + } + } + } + return false; + } + + private static boolean isMatchesInclude(int type, String input) { + if (input != null && input.length() > 0) { + for (int i = input.length() - 1; i >= 0; i--) { + if (isMatchesChar(type, input.charAt(i))) { + return true; + } + } + } + return false; + } + + /** + * 匹配正则表达式 + * + * @param regex + * @param input + * @return + */ + public static boolean isMatches( String regex, String input) { + return input == null ? false : Pattern.compile(regex).matcher(input).find(); + } + + /** + * 匹配正则表达式(忽略大小写) + * + * @param regex + * @param input + * @return + */ + public static boolean isMatchesIgnoreCase( String regex, String input) { + return input == null ? false : Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(input).find(); + } + + /** + * 是否是正则表达式中使用的特殊的计算符号,如:'$', '*', ... + * + * @param ch + * @return + */ + public static boolean isPatternRegexSpecialCharacter(char ch) { + for (int i = PATTERN_REGEX_SPECIAL_CHARACTERS.length - 1; i >= 0; i--) { + if (ch == PATTERN_REGEX_SPECIAL_CHARACTERS[i]) { + return true; + } + } + return false; + } + + /** + * 是否是标点符号,(POSIX 字符类(仅 US-ASCII) \p{Punct} 标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~) + * + * @param ch + * @return + */ + public static boolean isPunct(char ch) { + return (ch >= 0x21 && ch <= 0x2F) // !"#$%&'()*+,-./ + || (ch >= 0x3A && ch <= 0x40) // :;<=>?@ + || (ch >= 0x5B && ch <= 0x60) // [\]^_` + || (ch >= 0x7B && ch <= 0x7E);// {|}~ + } + + /** + * 是否是标点符号字符串 中文标点识别不了 + * + * @param input + * @return + */ + public static boolean isPunct( String input) { + return isMatches(TYPE_PUNCT, input); + } + + /** + * 是否包含标点符号字符串 + * + * @param input + * @return + */ + public static boolean isIncludePunct( String input) { + return isMatchesInclude(TYPE_PUNCT, input); + } + + /** + * 是否包含非字母,数字以及Punct规定的字符串 + * + * @param input + * @return 含有则返回true,否则 返回false + */ + public static boolean isIncludeInvalidChar( String input) { + return isMatchesInclude(TYPE_NOT_PUNCT_DIGIT_LETTER, input); + } + + /** + * 是否是数字字符串 + * + * @param input + * @return + */ + public static boolean isDigit( String input) { + return isMatches(TYPE_DIGIT, input); + } + + /** + * 是否包含数字字符串 + * + * @param input + * @return + */ + public static boolean isIncludeDigit( String input) { + return isMatchesInclude(TYPE_DIGIT, input); + } + + /** + * 是否是小写字母字符([a-z]) + * + * @param ch + * @return + */ + public static boolean isLowerLetter(char ch) { + return ch >= 0x61 && ch <= 0x7A; + } + + /** + * 是否是小写字母字符串([a-z]) + * + * @param input + * @return + */ + public static boolean isLowerLetter( String input) { + return isMatches(TYPE_LOWER_LETTER, input); + } + + /** + * 是否包含小写字母字符串([a-z]) + * + * @param input + * @return + */ + public static boolean isIncludeLowerLetter( String input) { + return isMatchesInclude(TYPE_LOWER_LETTER, input); + } + + /** + * 是否是大写字母字符([A-Z]) + * + * @param ch + * @return + */ + public static boolean isUpperLetter(char ch) { + return ch >= 0x41 && ch <= 0x5A; + } + + /** + * 是否是大写字母字符串([A-Z]) + * + * @param + * @return + */ + public static boolean isUpperLetter( String input) { + return isMatches(TYPE_UPPER_LETTER, input); + } + + /** + * 是否包含大写字母字符串([A-Z]) + * + * @param input + * @return + */ + public static boolean isIncludeUpperLetter( String input) { + return isMatchesInclude(TYPE_UPPER_LETTER, input); + } + + /** + * 是否是字母字符串([A-Za-z]) + * + * @param ch + * @return + */ + public static boolean isLetter(char ch) { + return isLowerLetter(ch) || isUpperLetter(ch); + } + + /** + * 是否是汉字字符(广义上的汉字字符,很多都无法用输入法输出的汉字) + * + * @param c + * @return + */ + public static boolean isCJK(char c) { + Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); + if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS // CJK统一汉字 \\u4E00-\\u9fAF + || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A // CJK统一汉字扩展-A \\u3400-\\u4dBF + || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS // CJK兼容汉字 \\uF900-\\uFAFF + || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION // CJK符号和标点 \\u3000-\\u303F + || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION // 广义标点 \\u2000-\\u206F + || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { // 半形及全形字符 \\uFF00-\\uFFEF + return true; + } + return false; + } + + /** + * 是否是汉字字符(不包含标点) + * + * @param c + * @return + */ + public static boolean isNoPunctuationCJK(char c) { + Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); + if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS // CJK统一汉字 \\u4E00-\\u9fAF + || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A // CJK统一汉字扩展-A \\u3400-\\u4dBF + || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS) { // CJK兼容汉字 \\uF900-\\uFAFF + return true; + } + return false; + } + + /** + * 是否是汉字字符(不包含标点) + * + * @param input + * @return + */ + public static boolean isNoPunctuationCJK( String input) { + return isMatches(TYPE_NO_PUNCTUATION_CJK, input); + } + + /** + * 是否是汉字字符串(广义上的汉字字符,很多都无法用输入法输出的汉字) + * + * @param input + * @return + */ + public static boolean isCJK( String input) { + return isMatches(TYPE_CJK, input); + } + + /** + * 是否包含汉字字符串(广义上的汉字字符,很多都无法用输入法输出的汉字) + * + * @param input + * @return + */ + public static boolean isIncludeCJK( String input) { + return isMatchesInclude(TYPE_CJK, input); + } + + /** + * 是否是空白字符 + * + * @param input + * @return + */ + public static boolean isWhitespace( String input) { + return isMatches(TYPE_WHITESPACE, input); + } + + /** + * 是否包含空白字符 + * + * @param input + * @return + */ + public static boolean isIncludeWhitespace( String input) { + return isMatchesInclude(TYPE_WHITESPACE, input); + } + + /** + * 是否符合密码规则 + * + * @param input + * @return + */ + public static boolean isFetionPassword( String input) { + // 密码必须含有字母和数字,符号可选,但是必须符合Punct标准,否则返回false + // 首先判断是否含有无效字符,含有,则返回false + if (isIncludeInvalidChar(input)) { + return false; + } + // 长度不符合要求,返回 false + if (input.length() < 6 || input.length() > 16) { + return false; + } + int tmpValue = 0; + if (isMatches("[A-Za-z]{1,15}", input)) { + tmpValue++; + } + if (isMatches("[0-9]{1,15}", input)) { + tmpValue++; + } + if (tmpValue >= 2) { + return true; + } else { + return false; + } + } + + /** + * 1个或者多个0-9组成的数字 + * + * @param str + * @return + */ + public static boolean isNumeric( String str) { + Pattern pattern = Pattern.compile("[0-9]+"); + Matcher isNum = pattern.matcher(str); + if (!isNum.matches()) { + return false; + } + return true; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ProcessUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ProcessUtils.java new file mode 100644 index 0000000000..3b9d54eaae --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ProcessUtils.java @@ -0,0 +1,86 @@ +package com.mogo.utils; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.content.Context; +import android.os.Process; +import android.text.TextUtils; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +public class ProcessUtils { + + public static boolean isMainProcess( Context context ) { + try { + ActivityManager activityManager = ( ( ActivityManager ) context.getSystemService( Context.ACTIVITY_SERVICE ) ); + if ( activityManager == null ) { + return false; + } + List< RunningAppProcessInfo > raps = activityManager.getRunningAppProcesses(); + if ( raps == null || raps.size() <= 0 ) { + return false; + } + int pid = Process.myPid(); + String packageName = context.getPackageName(); + for ( RunningAppProcessInfo info : raps ) { + if ( packageName.equals( info.processName ) ) { + return pid == info.pid; + } + } + return false; + } catch ( Exception e ) { + e.printStackTrace(); + } + + return false; + } + + public static String getPackageName() { + String packageName = null; + BufferedReader reader = null; + + try { + reader = new BufferedReader( new InputStreamReader( new FileInputStream( "/proc/" + Process.myPid() + "/cmdline" ) ) ); + packageName = reader.readLine().trim(); + } catch ( Exception e ) { + e.printStackTrace(); + packageName = String.valueOf( Process.myPid() ); + } finally { + if ( reader != null ) try { + reader.close(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + + return packageName; + } + + /** + * 获取进程号对应的进程名 + * + * @param pid 进程号 + * @return 进程名 + */ + private static String getProcessName( int pid ) { + BufferedReader reader = null; + try { + reader = new BufferedReader( new FileReader( "/proc/" + pid + "/cmdline" ) ); + String processName = reader.readLine(); + if ( !TextUtils.isEmpty( processName ) ) { + processName = processName.trim(); + } + return processName; + } catch ( Throwable t ) { + t.printStackTrace(); + } finally { + IOUtils.closeSilently( reader ); + } + return null; + } +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ReflectUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ReflectUtils.java new file mode 100644 index 0000000000..f3ee5ec450 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ReflectUtils.java @@ -0,0 +1,73 @@ +package com.mogo.utils; + +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + + +@SuppressWarnings("rawtypes") +public class ReflectUtils { + + public static Object getField( Class clazz, Object target, String name) throws Exception { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + public static void setField( Class clazz, Object target, String name, Object value) throws Exception { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } + + public static void setFieldNoException( Class clazz, Object target, String name, Object value) { + try { + ReflectUtils.setField(clazz, target, name, value); + } catch ( Exception e) { + Log.e("ReflectUtil",e.getMessage(),e); + } + } + + @SuppressWarnings("unchecked") + public static Object invoke( Class clazz, Object target, String name, Object... args) + throws Exception { + Class[] parameterTypes = null; + if (args != null) { + parameterTypes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + parameterTypes[i] = args[i].getClass(); + } + } + + Method method = clazz.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method.invoke(target, args); + } + + @SuppressWarnings("unchecked") + public static Object invoke( Class clazz, Object target, String name, Class[] parameterTypes, Object... args) + throws Exception { + Method method = clazz.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method.invoke(target, args); + } + + @SuppressWarnings("unchecked") + public static T invokeConstructor( Class clazz, Class[] parameterTypes, Object... args) + throws Exception { + Constructor constructor = clazz.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return (T) constructor.newInstance(args); + } + + @SuppressWarnings("unchecked") + public static T invokeConstructor( Class clazz) + throws Exception { + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return (T) constructor.newInstance(); + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ResourcesHelper.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ResourcesHelper.java new file mode 100644 index 0000000000..c46161df83 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ResourcesHelper.java @@ -0,0 +1,72 @@ +package com.mogo.utils; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; + +public class ResourcesHelper { + + public static Resources getResources( Context context){ + return context.getResources(); + } + + public static int getColor( Context context, int rid) { + return getResources(context).getColor(rid); + } + + public static ColorStateList getColorStateList( Context context, int rid) { + return getResources(context).getColorStateList(rid); + } + + public static String getString( Context context, int rid) { + return getResources(context).getString(rid); + } + + public static String getString( Context context, int rid, int param1, int param2) { + return getResources(context).getString(rid, param1, param2); + } + + public static String getString( Context context, int rid, String str) { + return getResources(context).getString(rid, str); + } + + public static String[] getStringArray( Context context, int rid) { + return getResources(context).getStringArray(rid); + } + + public static Drawable getDrawable( Context context, int rid) { + return getResources(context).getDrawable(rid); + } + + public static float getDimension( Context context, int rid) { + return getResources(context).getDimension(rid); + } + + public static DisplayMetrics getDisplayMetrics( Context context) { + return getResources(context).getDisplayMetrics(); + } + + public static int getDisplayMetrics( Context context, int x) { + return getResources(context).getDimensionPixelSize(x); + } + + public static int getDimensionPixelSize( Context context, int x) { + return getResources(context).getDimensionPixelSize(x); + } + + public static int getInteger( Context context, int rid) { + return getResources(context).getInteger(rid); + } + + public static XmlResourceParser getXml( Context context, int rid) { + return getResources(context).getXml(rid); + } + + public static Configuration getConfiguration( Context context) { + return getResources(context).getConfiguration(); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/SoftKeyboardUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/SoftKeyboardUtils.java new file mode 100644 index 0000000000..ed4cf52ce4 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/SoftKeyboardUtils.java @@ -0,0 +1,33 @@ +package com.mogo.utils; + +import android.content.Context; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +/** + * Created by congtaowang on 2018/12/20. + */ +public class SoftKeyboardUtils { + + public static void show( Context context ) { + try { + InputMethodManager imm = ( InputMethodManager ) context.getSystemService( Context.INPUT_METHOD_SERVICE ); + if ( imm != null ) { + imm.toggleSoftInput( 0, InputMethodManager.HIDE_NOT_ALWAYS ); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + } + + public static void hidden( Context context, EditText editText ) { + try { + InputMethodManager imm = ( InputMethodManager ) context.getSystemService( Context.INPUT_METHOD_SERVICE ); + if ( imm != null ) { + imm.hideSoftInputFromWindow( editText.getWindowToken(), 0 ); + } + } catch ( Exception e ) { + e.printStackTrace(); + } + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ThreadPoolService.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ThreadPoolService.java new file mode 100644 index 0000000000..3fb2341761 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ThreadPoolService.java @@ -0,0 +1,16 @@ +package com.mogo.utils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ThreadPoolService { + + private static final ExecutorService SERVICE = Executors.newScheduledThreadPool( 1 ); + + private ThreadPoolService() { + } + + public static void execute( Runnable task ) { + SERVICE.execute( task ); + } +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/TipToast.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/TipToast.java new file mode 100644 index 0000000000..93af03bbae --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/TipToast.java @@ -0,0 +1,176 @@ +package com.mogo.utils; + +/** + * 2016/1/1 by congtaowang + * + * @Version 1.0 + */ + +import android.content.Context; +import android.os.Handler; +import android.text.TextUtils; +import android.view.View; +import android.widget.Toast; + +import com.mogo.utils.logger.Logger; + + +/** + * Init TipToast in your Application + */ +public final class TipToast { + + private static final String TAG = "TipToast"; + + private static Toast sToast = null; + private static final Object sSyncObject = new Object(); + private static Handler sHandler = null; + private static Context sContext; + private static ToastViewGenerator sGenerator; + + public static void init( Context context, ToastViewGenerator generator ) { + TipToast.sContext = context; + sHandler = new Handler( context.getMainLooper() ); + sGenerator = generator; + } + + public static void recycle() { + sContext = null; + sHandler = null; + if ( sToast != null ) { + sToast.cancel(); + sToast = null; + } + sGenerator = null; + } + + private static void tip( final String message, int duration ) { + if ( !checkParams() ) { + return; + } + if ( TextUtils.isEmpty( message ) ) { + return; + } + new ToastThread( new StringToastRunnable( sContext, message, duration ) ).start(); + } + + private static void tip( final int msgId, int duration ) { + if ( !checkParams() ) { + return; + } + try { + if ( TextUtils.isEmpty( ResourcesHelper.getString( sContext, msgId ) ) ) { + return; + } + } catch ( Exception e ) { + return; + } + tip( ResourcesHelper.getString( sContext, msgId ), duration ); + } + + private static boolean checkParams() { + if ( sContext == null ) { + Logger.e( TAG, "context can't be null." ); + return false; + } + if ( sHandler == null ) { + Logger.e( TAG, "sHandler can't be null." ); + return false; + } + return true; + } + + public static void tip( final String message ) { + tip( message, Toast.LENGTH_SHORT ); + } + + public static void tip( final int msgId ) { + tip( msgId, Toast.LENGTH_SHORT ); + } + + public static void longTip( String message ) { + tip( message, Toast.LENGTH_LONG ); + } + + public static void longTip( int msgId ) { + tip( msgId, Toast.LENGTH_LONG ); + } + + public static void shortTip( String message ) { + tip( message, Toast.LENGTH_SHORT ); + } + + public static void shortTip( int msgId ) { + tip( msgId, Toast.LENGTH_SHORT ); + } + + static class ToastThread extends Thread { + public ToastThread( Runnable runnable ) { + super( runnable ); + } + + } + + static class StringToastRunnable implements Runnable { + + Context context; + String msg; + int duration; + + public StringToastRunnable( Context context, String msg, int duration ) { + this.context = context; + this.msg = msg; + this.duration = duration; + } + + @Override + public void run() { + + if ( sHandler == null ) { + return; + } + + sHandler.post( new Runnable() { + + @Override + public void run() { + synchronized ( sSyncObject ) { + + if ( context == null ) { + return; + } + + if ( sToast != null ) { + sToast.cancel(); + } + + if ( sGenerator == null ) { + sToast = Toast.makeText( context, msg, duration ); + } + + if ( sGenerator == null ) { + sToast = Toast.makeText( context, msg, duration ); + } else { + sToast = new Toast( context ); + final View view = sGenerator.make( context, msg ); + if ( view != null ) { + sToast.setView( view ); + sToast.setDuration( duration ); + } else { + sToast = Toast.makeText( context, msg, duration ); + } + } + if ( sToast != null ) { + sToast.show(); + } + } + } + } ); + } + } + + public interface ToastViewGenerator { + View make( Context context, String message ); + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/UiThreadHandler.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/UiThreadHandler.java new file mode 100644 index 0000000000..7225548d91 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/UiThreadHandler.java @@ -0,0 +1,41 @@ +package com.mogo.utils; + +import android.os.Handler; +import android.os.Looper; + +public class UiThreadHandler { + + private static Handler sUiHandler = new Handler( Looper.getMainLooper() ); + + private static Object sToken = new Object(); + + public UiThreadHandler() { + } + + public static boolean post( Runnable r ) { + return sUiHandler != null && sUiHandler.post( r ); + } + + public static boolean postDelayed( Runnable r, long delayMillis ) { + return sUiHandler != null && sUiHandler.postDelayed( r, delayMillis ); + } + + public static Handler getsUiHandler() { + return sUiHandler; + } + + public static boolean postOnceDelayed( Runnable r, long delayMillis ) { + if ( sUiHandler == null ) { + return false; + } else { + sUiHandler.removeCallbacks( r, sToken ); + return sUiHandler.postDelayed( r, delayMillis ); + } + } + + public static void removeCallbacks( Runnable runnable ) { + if ( sUiHandler != null ) { + sUiHandler.removeCallbacks( runnable ); + } + } +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/ValidateUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/ValidateUtils.java new file mode 100644 index 0000000000..2bf7c332c6 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/ValidateUtils.java @@ -0,0 +1,10 @@ +package com.mogo.utils; + +/** + * @author congtaowang + * @since 2019-06-11 + *

+ * 验证格式工具类 + */ +public class ValidateUtils { +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/WindowUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/WindowUtils.java new file mode 100644 index 0000000000..65416700e8 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/WindowUtils.java @@ -0,0 +1,71 @@ +package com.mogo.utils; + +import android.content.Context; + +public class WindowUtils { + + public static int getStatusBarHeight( Context context ) { + if ( context == null ) { + return 0; + } else { + int result = 0; + int resourceId = context.getResources().getIdentifier( "status_bar_height", "dimen", "android" ); + if ( resourceId > 0 ) { + result = context.getResources().getDimensionPixelSize( resourceId ); + } + + return result; + } + } + + /** + * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + */ + public static int dip2px( Context context, float dpValue ) { + if ( context == null ) { + return 0; + } + final float scale = context.getResources().getDisplayMetrics().density; + return ( int ) ( dpValue * scale + 0.5f ); + } + + /** + * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + */ + public static int px2dip( Context context, float pxValue ) { + if ( context == null ) { + return 0; + } + final float scale = context.getResources().getDisplayMetrics().density; + return ( int ) ( pxValue / scale + 0.5f ); + } + + + public static String getScreenPixels( Context context ) { + if ( context == null ) { + return ""; + } + return getScreenWidth( context ) + "*" + getScreenHeight( context ); + } + + public static int getScreenWidth( Context context ) { + if ( context == null ) { + return 0; + } + return context.getResources().getDisplayMetrics().widthPixels; + } + + public static int getScreenHeight( Context context ) { + if ( context == null ) { + return 0; + } + return context.getResources().getDisplayMetrics().heightPixels; + } + + public static int getScreenDpi( Context context ) { + if ( context == null ) { + return 0; + } + return context.getResources().getDisplayMetrics().densityDpi; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryDecoder.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryDecoder.java new file mode 100644 index 0000000000..9e4cf08970 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryDecoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Defines common decoding methods for byte array decoders. + * + * @version $Id: BinaryDecoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface BinaryDecoder extends Decoder { + + /** + * Decodes a byte array and returns the results as a byte array. + * + * @param source + * A byte array which has been encoded with the appropriate encoder + * @return a byte array that contains decoded content + * @throws DecoderException + * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. + */ + byte[] decode( byte[] source ) throws DecoderException; +} + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryEncoder.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryEncoder.java new file mode 100644 index 0000000000..5d40f71c5e --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/BinaryEncoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Defines common encoding methods for byte array encoders. + * + * @version $Id: BinaryEncoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface BinaryEncoder extends Encoder { + + /** + * Encodes a byte array and return the encoded data as a byte array. + * + * @param source + * Data to be encoded + * @return A byte array containing the encoded data + * @throws EncoderException + * thrown if the Encoder encounters a failure condition during the encoding process. + */ + byte[] encode( byte[] source ) throws EncoderException; +} + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharEncoding.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharEncoding.java new file mode 100644 index 0000000000..80a0081204 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharEncoding.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Character encoding names required of every implementation of the Java platform. + * + * From the Java documentation Standard charsets: + *

+ * Every implementation of the Java platform is required to support the following character encodings. Consult the + * release documentation for your implementation to see if any other encodings are supported. Consult the release + * documentation for your implementation to see if any other encodings are supported. + *

+ * + *
    + *
  • US-ASCII
    + * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • + *
  • ISO-8859-1
    + * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • + *
  • UTF-8
    + * Eight-bit Unicode Transformation Format.
  • + *
  • UTF-16BE
    + * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • + *
  • UTF-16LE
    + * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • + *
  • UTF-16
    + * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order + * accepted on input, big-endian used on output.)
  • + *
+ * + * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in [lang], it is not + * foreseen that [codec] would be made to depend on [lang]. + * + *

+ * This class is immutable and thread-safe. + *

+ * + * @see Standard charsets + * @since 1.4 + * @version $Id: CharEncoding.java 1563226 2014-01-31 19:38:06Z ggregory $ + */ +public class CharEncoding { + /** + * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String ISO_8859_1 = "ISO-8859-1"; + + /** + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String US_ASCII = "US-ASCII"; + + /** + * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark + * (either order accepted on input, big-endian used on output) + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_16 = "UTF-16"; + + /** + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_16BE = "UTF-16BE"; + + /** + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_16LE = "UTF-16LE"; + + /** + * Eight-bit Unicode Transformation Format. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + */ + public static final String UTF_8 = "UTF-8"; +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharSequenceUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharSequenceUtils.java new file mode 100644 index 0000000000..bf5d9c3f3d --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/CharSequenceUtils.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mogo.utils.digest; + +/** + *

+ * Operations on {@link CharSequence} that are null safe. + *

+ *

+ * Copied from Apache Commons Lang r1586295 on April 10, 2014 (day of 3.3.2 release). + *

+ * + * @see CharSequence + * @since 1.10 + */ +public class CharSequenceUtils { + + /** + * Green implementation of regionMatches. + * + * @param cs + * the CharSequence to be processed + * @param ignoreCase + * whether or not to be case insensitive + * @param thisStart + * the index to start on the cs CharSequence + * @param substring + * the CharSequence to be looked for + * @param start + * the index to start on the substring CharSequence + * @param length + * character length of the region + * @return whether the region matched + */ + static boolean regionMatches( final CharSequence cs, final boolean ignoreCase, final int thisStart, + final CharSequence substring, final int start, final int length) { + if (cs instanceof String && substring instanceof String ) { + return (( String ) cs).regionMatches(ignoreCase, thisStart, ( String ) substring, start, length); + } + int index1 = thisStart; + int index2 = start; + int tmpLen = length; + + while (tmpLen-- > 0) { + char c1 = cs.charAt(index1++); + char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + + if (!ignoreCase) { + return false; + } + + // The same check as in String.regionMatches(): + if ( Character.toUpperCase(c1) != Character.toUpperCase(c2) && + Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + + return true; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Charsets.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Charsets.java new file mode 100644 index 0000000000..83104fb837 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Charsets.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mogo.utils.digest; + +import java.nio.charset.Charset; + +/** + * Charsets required of every implementation of the Java platform. + * + * From the Java documentation Standard + * charsets: + *

+ * Every implementation of the Java platform is required to support the following character encodings. Consult the + * release documentation for your implementation to see if any other encodings are supported. Consult the release + * documentation for your implementation to see if any other encodings are supported. + *

+ * + *
    + *
  • US-ASCII
    + * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • + *
  • ISO-8859-1
    + * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • + *
  • UTF-8
    + * Eight-bit Unicode Transformation Format.
  • + *
  • UTF-16BE
    + * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • + *
  • UTF-16LE
    + * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • + *
  • UTF-16
    + * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order + * accepted on input, big-endian used on output.)
  • + *
+ * + * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in Commons Lang, it is + * not foreseen that Commons Codec would be made to depend on Commons Lang. + * + *

+ * This class is immutable and thread-safe. + *

+ * + * @see Standard charsets + * @since 1.7 + * @version $Id: CharEncoding.java 1173287 2011-09-20 18:16:19Z ggregory $ + */ +public class Charsets { + + // + // This class should only contain Charset instances for required encodings. This guarantees that it will load + // correctly and without delay on all Java platforms. + // + + /** + * Returns the given Charset or the default Charset if the given Charset is null. + * + * @param charset + * A charset or null. + * @return the given Charset or the default Charset if the given Charset is null + */ + public static Charset toCharset( final Charset charset) { + return charset == null ? Charset.defaultCharset() : charset; + } + + /** + * Returns a Charset for the named charset. If the name is null, return the default Charset. + * + * @param charset + * The name of the requested charset, may be null. + * @return a Charset for the named charset + * @throws java.nio.charset.UnsupportedCharsetException + * If the named charset is unavailable + */ + public static Charset toCharset( final String charset) { + return charset == null ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset ISO_8859_1 = Charset.forName(CharEncoding.ISO_8859_1); + + /** + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII); + + /** + * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark + * (either order accepted on input, big-endian used on output) + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_16 = Charset.forName(CharEncoding.UTF_16); + + /** + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_16BE = Charset.forName(CharEncoding.UTF_16BE); + + /** + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_16LE = Charset.forName(CharEncoding.UTF_16LE); + + /** + * Eight-bit Unicode Transformation Format. + *

+ * Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard charsets + * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + */ + @Deprecated + public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8); +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Decoder.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Decoder.java new file mode 100644 index 0000000000..fbb68cacfa --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Decoder.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Provides the highest level of abstraction for Decoders. + *

+ * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. + * Allows a user to pass a generic Object to any Decoder implementation in the codec package. + *

+ * One of the two interfaces at the center of the codec package. + * + * @version $Id: Decoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface Decoder { + + /** + * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will + * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a + * {@link ClassCastException} occurs this decode method will throw a DecoderException. + * + * @param source + * the object to decode + * @return a 'decoded" object + * @throws DecoderException + * a decoder exception can be thrown for any number of reasons. Some good candidates are that the + * parameter passed to this method is null, a param cannot be cast to the appropriate type for a + * specific encoder. + */ + Object decode( Object source ) throws DecoderException; +} + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DecoderException.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DecoderException.java new file mode 100644 index 0000000000..4bbfb808ed --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DecoderException.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} + * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. + * + * @version $Id: DecoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class DecoderException extends Exception { + + /** + * Declares the Serial Version Uid. + * + * @see Always Declare Serial Version Uid + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with null as its detail message. The cause is not initialized, and may + * subsequently be initialized by a call to {@link #initCause}. + * + * @since 1.4 + */ + public DecoderException() { + super(); + } + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently + * be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message which is saved for later retrieval by the {@link #getMessage()} method. + */ + public DecoderException(final String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically incorporated into this + * exception's detail message. + * + * @param message + * The detail message which is saved for later retrieval by the {@link #getMessage()} method. + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public DecoderException( final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail message of (cause==null ? + * null : cause.toString()) (which typically contains the class and detail message of cause). + * This constructor is useful for exceptions that are little more than wrappers for other throwables. + * + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public DecoderException(final Throwable cause) { + super(cause); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DigestUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DigestUtils.java new file mode 100644 index 0000000000..5ee2a590f1 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/DigestUtils.java @@ -0,0 +1,817 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Operations to simplify common {@link MessageDigest} tasks. + * This class is immutable and thread-safe. + * + * @version $Id: DigestUtils.java 1634433 2014-10-27 01:10:47Z ggregory $ + */ +public class DigestUtils { + + private static final int STREAM_BUFFER_LENGTH = 1024; + + /** + * Read through an InputStream and returns the digest for the data + * + * @param digest + * The MessageDigest to use (e.g. MD5) + * @param data + * Data to digest + * @return the digest + * @throws IOException + * On error reading from the stream + */ + private static byte[] digest( final MessageDigest digest, final InputStream data) throws IOException { + return updateDigest(digest, data).digest(); + } + + /** + * Returns a MessageDigest for the given algorithm. + * + * @param algorithm + * the name of the algorithm requested. See Appendix A in the Java Cryptography Architecture Reference Guide for information about standard + * algorithm names. + * @return A digest instance. + * @see MessageDigest#getInstance(String) + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught. + */ + public static MessageDigest getDigest( final String algorithm) { + try { + return MessageDigest.getInstance(algorithm); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Returns an MD2 MessageDigest. + * + * @return An MD2 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD2 is a + * built-in algorithm + * @see MessageDigestAlgorithms#MD2 + * @since 1.7 + */ + public static MessageDigest getMd2Digest() { + return getDigest(MessageDigestAlgorithms.MD2); + } + + /** + * Returns an MD5 MessageDigest. + * + * @return An MD5 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD5 is a + * built-in algorithm + * @see MessageDigestAlgorithms#MD5 + */ + public static MessageDigest getMd5Digest() { + return getDigest(MessageDigestAlgorithms.MD5); + } + + /** + * Returns an SHA-1 digest. + * + * @return An SHA-1 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-1 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_1 + * @since 1.7 + */ + public static MessageDigest getSha1Digest() { + return getDigest(MessageDigestAlgorithms.SHA_1); + } + + /** + * Returns an SHA-256 digest. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @return An SHA-256 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-256 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_256 + */ + public static MessageDigest getSha256Digest() { + return getDigest(MessageDigestAlgorithms.SHA_256); + } + + /** + * Returns an SHA-384 digest. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @return An SHA-384 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-384 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_384 + */ + public static MessageDigest getSha384Digest() { + return getDigest(MessageDigestAlgorithms.SHA_384); + } + + /** + * Returns an SHA-512 digest. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @return An SHA-512 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-512 is a + * built-in algorithm + * @see MessageDigestAlgorithms#SHA_512 + */ + public static MessageDigest getSha512Digest() { + return getDigest(MessageDigestAlgorithms.SHA_512); + } + + /** + * Returns an SHA-1 digest. + * + * @return An SHA-1 digest instance. + * @throws IllegalArgumentException + * when a {@link NoSuchAlgorithmException} is caught + * @deprecated Use {@link #getSha1Digest()} + */ + @Deprecated + public static MessageDigest getShaDigest() { + return getSha1Digest(); + } + + /** + * Calculates the MD2 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD2 digest + * @since 1.7 + */ + public static byte[] md2(final byte[] data) { + return getMd2Digest().digest(data); + } + + /** + * Calculates the MD2 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD2 digest + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static byte[] md2(final InputStream data) throws IOException { + return digest(getMd2Digest(), data); + } + + /** + * Calculates the MD2 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return MD2 digest + * @since 1.7 + */ + public static byte[] md2(final String data) { + return md2(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the MD2 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD2 digest as a hex string + * @since 1.7 + */ + public static String md2Hex( final byte[] data) { + return Hex.encodeHexString(md2(data)); + } + + /** + * Calculates the MD2 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD2 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static String md2Hex( final InputStream data) throws IOException { + return Hex.encodeHexString(md2(data)); + } + + /** + * Calculates the MD2 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD2 digest as a hex string + * @since 1.7 + */ + public static String md2Hex( final String data) { + return Hex.encodeHexString(md2(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD5 digest + */ + public static byte[] md5(final byte[] data) { + return getMd5Digest().digest(data); + } + + /** + * Calculates the MD5 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest + * @return MD5 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] md5(final InputStream data) throws IOException { + return digest(getMd5Digest(), data); + } + + /** + * Calculates the MD5 digest and returns the value as a 16 element byte[]. + * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return MD5 digest + */ + public static byte[] md5(final String data) { + return md5(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD5 digest as a hex string + */ + public static String md5Hex( final byte[] data) { + return Hex.encodeHexString(md5(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD5 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String md5Hex( final InputStream data) throws IOException { + return Hex.encodeHexString(md5(data)); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param data + * Data to digest + * @return MD5 digest as a hex string + */ + public static String md5Hex( final String data) { + return Hex.encodeHexString(md5(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @deprecated Use {@link #sha1(byte[])} + */ + @Deprecated + public static byte[] sha(final byte[] data) { + return sha1(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + * @deprecated Use {@link #sha1(InputStream)} + */ + @Deprecated + public static byte[] sha(final InputStream data) throws IOException { + return sha1(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @deprecated Use {@link #sha1(String)} + */ + @Deprecated + public static byte[] sha(final String data) { + return sha1(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @since 1.7 + */ + public static byte[] sha1(final byte[] data) { + return getSha1Digest().digest(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest + * @return SHA-1 digest + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static byte[] sha1(final InputStream data) throws IOException { + return digest(getSha1Digest(), data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a byte[]. + * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-1 digest + */ + public static byte[] sha1(final String data) { + return sha1(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @since 1.7 + */ + public static String sha1Hex( final byte[] data) { + return Hex.encodeHexString(sha1(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.7 + */ + public static String sha1Hex( final InputStream data) throws IOException { + return Hex.encodeHexString(sha1(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @since 1.7 + */ + public static String sha1Hex( final String data) { + return Hex.encodeHexString(sha1(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest + * @since 1.4 + */ + public static byte[] sha256(final byte[] data) { + return getSha256Digest().digest(data); + } + + /** + * Calculates the SHA-256 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] sha256(final InputStream data) throws IOException { + return digest(getSha256Digest(), data); + } + + /** + * Calculates the SHA-256 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-256 digest + * @since 1.4 + */ + public static byte[] sha256(final String data) { + return sha256(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest as a hex string + * @since 1.4 + */ + public static String sha256Hex( final byte[] data) { + return Hex.encodeHexString(sha256(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String sha256Hex( final InputStream data) throws IOException { + return Hex.encodeHexString(sha256(data)); + } + + /** + * Calculates the SHA-256 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-256 digest as a hex string + * @since 1.4 + */ + public static String sha256Hex( final String data) { + return Hex.encodeHexString(sha256(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest + * @since 1.4 + */ + public static byte[] sha384(final byte[] data) { + return getSha384Digest().digest(data); + } + + /** + * Calculates the SHA-384 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] sha384(final InputStream data) throws IOException { + return digest(getSha384Digest(), data); + } + + /** + * Calculates the SHA-384 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-384 digest + * @since 1.4 + */ + public static byte[] sha384(final String data) { + return sha384(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest as a hex string + * @since 1.4 + */ + public static String sha384Hex( final byte[] data) { + return Hex.encodeHexString(sha384(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String sha384Hex( final InputStream data) throws IOException { + return Hex.encodeHexString(sha384(data)); + } + + /** + * Calculates the SHA-384 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-384 digest as a hex string + * @since 1.4 + */ + public static String sha384Hex( final String data) { + return Hex.encodeHexString(sha384(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest + * @since 1.4 + */ + public static byte[] sha512(final byte[] data) { + return getSha512Digest().digest(data); + } + + /** + * Calculates the SHA-512 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static byte[] sha512(final InputStream data) throws IOException { + return digest(getSha512Digest(), data); + } + + /** + * Calculates the SHA-512 digest and returns the value as a byte[]. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return SHA-512 digest + * @since 1.4 + */ + public static byte[] sha512(final String data) { + return sha512(StringUtils.getBytesUtf8(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest as a hex string + * @since 1.4 + */ + public static String sha512Hex( final byte[] data) { + return Hex.encodeHexString(sha512(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + */ + public static String sha512Hex( final InputStream data) throws IOException { + return Hex.encodeHexString(sha512(data)); + } + + /** + * Calculates the SHA-512 digest and returns the value as a hex string. + *

+ * Throws a RuntimeException on JRE versions prior to 1.4.0. + *

+ * + * @param data + * Data to digest + * @return SHA-512 digest as a hex string + * @since 1.4 + */ + public static String sha512Hex( final String data) { + return Hex.encodeHexString(sha512(data)); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @deprecated Use {@link #sha1Hex(byte[])} + */ + @Deprecated + public static String shaHex( final byte[] data) { + return sha1Hex(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @throws IOException + * On error reading from the stream + * @since 1.4 + * @deprecated Use {@link #sha1Hex(InputStream)} + */ + @Deprecated + public static String shaHex( final InputStream data) throws IOException { + return sha1Hex(data); + } + + /** + * Calculates the SHA-1 digest and returns the value as a hex string. + * + * @param data + * Data to digest + * @return SHA-1 digest as a hex string + * @deprecated Use {@link #sha1Hex(String)} + */ + @Deprecated + public static String shaHex( final String data) { + return sha1Hex(data); + } + + /** + * Updates the given {@link MessageDigest}. + * + * @param messageDigest + * the {@link MessageDigest} to update + * @param valueToDigest + * the value to update the {@link MessageDigest} with + * @return the updated {@link MessageDigest} + * @since 1.7 + */ + public static MessageDigest updateDigest( final MessageDigest messageDigest, final byte[] valueToDigest) { + messageDigest.update(valueToDigest); + return messageDigest; + } + + /** + * Reads through an InputStream and updates the digest for the data + * + * @param digest + * The MessageDigest to use (e.g. MD5) + * @param data + * Data to digest + * @return the digest + * @throws IOException + * On error reading from the stream + * @since 1.8 + */ + public static MessageDigest updateDigest( final MessageDigest digest, final InputStream data) throws IOException { + final byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; + int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); + + while (read > -1) { + digest.update(buffer, 0, read); + read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); + } + + return digest; + } + + /** + * Updates the given {@link MessageDigest}. + * + * @param messageDigest + * the {@link MessageDigest} to update + * @param valueToDigest + * the value to update the {@link MessageDigest} with; + * converted to bytes using {@link StringUtils#getBytesUtf8(String)} + * @return the updated {@link MessageDigest} + * @since 1.7 + */ + public static MessageDigest updateDigest( final MessageDigest messageDigest, final String valueToDigest) { + messageDigest.update(StringUtils.getBytesUtf8(valueToDigest)); + return messageDigest; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Encoder.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Encoder.java new file mode 100644 index 0000000000..115d49c620 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Encoder.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Provides the highest level of abstraction for Encoders. + *

+ * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this + * common generic interface which allows a user to pass a generic Object to any Encoder implementation + * in the codec package. + * + * @version $Id: Encoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface Encoder { + + /** + * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be + * byte[] or Strings depending on the implementation used. + * + * @param source + * An object to encode + * @return An "encoded" Object + * @throws EncoderException + * An encoder exception is thrown if the encoder experiences a failure condition during the encoding + * process. + */ + Object encode( Object source ) throws EncoderException; +} + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/EncoderException.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/EncoderException.java new file mode 100644 index 0000000000..babc0bcc23 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/EncoderException.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Thrown when there is a failure condition during the encoding process. This exception is thrown when an + * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, + * characters outside of the expected range. + * + * @version $Id: EncoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class EncoderException extends Exception { + + /** + * Declares the Serial Version Uid. + * + * @see Always Declare Serial Version Uid + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with null as its detail message. The cause is not initialized, and may + * subsequently be initialized by a call to {@link #initCause}. + * + * @since 1.4 + */ + public EncoderException() { + super(); + } + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently + * be initialized by a call to {@link #initCause}. + * + * @param message + * a useful message relating to the encoder specific error. + */ + public EncoderException(final String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + * + *

+ * Note that the detail message associated with cause is not automatically incorporated into this + * exception's detail message. + *

+ * + * @param message + * The detail message which is saved for later retrieval by the {@link #getMessage()} method. + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public EncoderException( final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail message of (cause==null ? + * null : cause.toString()) (which typically contains the class and detail message of cause). + * This constructor is useful for exceptions that are little more than wrappers for other throwables. + * + * @param cause + * The cause which is saved for later retrieval by the {@link #getCause()} method. A null + * value is permitted, and indicates that the cause is nonexistent or unknown. + * @since 1.4 + */ + public EncoderException(final Throwable cause) { + super(cause); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Hex.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Hex.java new file mode 100644 index 0000000000..9879ed8f93 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/Hex.java @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + + +import java.nio.charset.Charset; + +/** + * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in + * {@link #DEFAULT_CHARSET_NAME} + * + * This class is thread-safe. + * + * @since 1.1 + * @version $Id: Hex.java 1619948 2014-08-22 22:53:55Z ggregory $ + */ +public class Hex implements BinaryEncoder, BinaryDecoder { + + /** + * Default charset name is {@link Charsets#UTF_8} + * + * @since 1.7 + */ + public static final Charset DEFAULT_CHARSET = Charsets.UTF_8; + + /** + * Default charset name is {@link CharEncoding#UTF_8} + * + * @since 1.4 + */ + public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_LOWER = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_UPPER = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + /** + * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The + * returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param data + * An array of characters containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied char array. + * @throws DecoderException + * Thrown if an odd number or illegal of characters is supplied + */ + public static byte[] decodeHex(final char[] data) throws DecoderException { + + final int len = data.length; + + if ((len & 0x01) != 0) { + throw new DecoderException("Odd number of characters."); + } + + final byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f = f | toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A char[] containing hexadecimal characters + */ + public static char[] encodeHex(final byte[] data) { + return encodeHex(data, true); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @param toLowerCase + * true converts to lowercase, false to uppercase + * @return A char[] containing hexadecimal characters + * @since 1.4 + */ + public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @param toDigits + * the output alphabet + * @return A char[] containing hexadecimal characters + * @since 1.4 + */ + protected static char[] encodeHex(final byte[] data, final char[] toDigits) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + /** + * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned + * String will be double the length of the passed array, as it takes two characters to represent any given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A String containing hexadecimal characters + * @since 1.4 + */ + public static String encodeHexString( final byte[] data) { + return new String(encodeHex(data)); + } + + /** + * Converts a hexadecimal character to an integer. + * + * @param ch + * A character to convert to an integer digit + * @param index + * The index of the character in the source + * @return An integer + * @throws DecoderException + * Thrown if ch is an illegal hex character + */ + protected static int toDigit(final char ch, final int index) throws DecoderException { + final int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index); + } + return digit; + } + + private final Charset charset; + + /** + * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET} + */ + public Hex() { + // use default encoding + this.charset = DEFAULT_CHARSET; + } + + /** + * Creates a new codec with the given Charset. + * + * @param charset + * the charset. + * @since 1.7 + */ + public Hex(final Charset charset) { + this.charset = charset; + } + + /** + * Creates a new codec with the given charset name. + * + * @param charsetName + * the charset name. + * @throws java.nio.charset.UnsupportedCharsetException + * If the named charset is unavailable + * @since 1.4 + * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable + */ + public Hex(final String charsetName) { + this( Charset.forName(charsetName)); + } + + /** + * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values. + * The returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param array + * An array of character bytes containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied byte array (representing characters). + * @throws DecoderException + * Thrown if an odd number of characters is supplied to this function + * @see #decodeHex(char[]) + */ + @Override + public byte[] decode(final byte[] array) throws DecoderException { + return decodeHex(new String(array, getCharset()).toCharArray()); + } + + /** + * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those + * same values. The returned array will be half the length of the passed String or array, as it takes two characters + * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param object + * A String or, an array of character bytes containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied byte array (representing characters). + * @throws DecoderException + * Thrown if an odd number of characters is supplied to this function or the object is not a String or + * char[] + * @see #decodeHex(char[]) + */ + @Override + public Object decode( final Object object) throws DecoderException { + try { + final char[] charArray = object instanceof String ? (( String ) object).toCharArray() : (char[]) object; + return decodeHex(charArray); + } catch (final ClassCastException e) { + throw new DecoderException(e.getMessage(), e); + } + } + + /** + * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each + * byte in order. The returned array will be double the length of the passed array, as it takes two characters to + * represent any given byte. + *

+ * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by + * {@link #getCharset()}. + *

+ * + * @param array + * a byte[] to convert to Hex characters + * @return A byte[] containing the bytes of the hexadecimal characters + * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid. + * @see #encodeHex(byte[]) + */ + @Override + public byte[] encode(final byte[] array) { + return encodeHexString(array).getBytes(this.getCharset()); + } + + /** + * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each + * byte in order. The returned array will be double the length of the passed String or array, as it takes two + * characters to represent any given byte. + *

+ * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by + * {@link #getCharset()}. + *

+ * + * @param object + * a String, or byte[] to convert to Hex characters + * @return A char[] containing hexadecimal characters + * @throws EncoderException + * Thrown if the given object is not a String or byte[] + * @see #encodeHex(byte[]) + */ + @Override + public Object encode( final Object object) throws EncoderException { + try { + final byte[] byteArray = object instanceof String ? + (( String ) object).getBytes(this.getCharset()) : (byte[]) object; + return encodeHex(byteArray); + } catch (final ClassCastException e) { + throw new EncoderException(e.getMessage(), e); + } + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 1.7 + */ + public Charset getCharset() { + return this.charset; + } + + /** + * Gets the charset name. + * + * @return the charset name. + * @since 1.4 + */ + public String getCharsetName() { + return this.charset.name(); + } + + /** + * Returns a string representation of the object, which includes the charset name. + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return super.toString() + "[charsetName=" + this.charset + "]"; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/MessageDigestAlgorithms.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/MessageDigestAlgorithms.java new file mode 100644 index 0000000000..9ecd4e4f9b --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/MessageDigestAlgorithms.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +import java.security.MessageDigest; + +/** + * Standard {@link MessageDigest} algorithm names from the Java Cryptography Architecture Standard Algorithm Name + * Documentation. + *

+ * This class is immutable and thread-safe. + *

+ * + * @see Java Cryptography + * Architecture Standard Algorithm Name Documentation + * @since 1.7 + * @version $Id: MessageDigestAlgorithms.java 1585867 2014-04-09 00:12:36Z ggregory $ + */ +public class MessageDigestAlgorithms { + + private MessageDigestAlgorithms() { + // cannot be instantiated. + } + + /** + * The MD2 message digest algorithm defined in RFC 1319. + */ + public static final String MD2 = "MD2"; + + /** + * The MD5 message digest algorithm defined in RFC 1321. + */ + public static final String MD5 = "MD5"; + + /** + * The SHA-1 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_1 = "SHA-1"; + + /** + * The SHA-256 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_256 = "SHA-256"; + + /** + * The SHA-384 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_384 = "SHA-384"; + + /** + * The SHA-512 hash algorithm defined in the FIPS PUB 180-2. + */ + public static final String SHA_512 = "SHA-512"; + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringEncoder.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringEncoder.java new file mode 100644 index 0000000000..91dfa24b4f --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringEncoder.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + +/** + * Defines common encoding methods for String encoders. + * + * @version $Id: StringEncoder.java 1379145 2012-08-30 21:02:52Z tn $ + */ +public interface StringEncoder extends Encoder { + + /** + * Encodes a String and returns a String. + * + * @param source + * the String to encode + * @return the encoded String + * @throws EncoderException + * thrown if there is an error condition during the encoding process. + */ + String encode( String source ) throws EncoderException; +} + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringUtils.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringUtils.java new file mode 100644 index 0000000000..c847ec094c --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/digest/StringUtils.java @@ -0,0 +1,384 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mogo.utils.digest; + + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +/** + * Converts String to and from bytes using the encodings required by the Java specification. These encodings are + * specified in + * Standard charsets. + * + *

This class is immutable and thread-safe.

+ * + * @see CharEncoding + * @see Standard charsets + * @version $Id: StringUtils.java 1634456 2014-10-27 05:26:56Z ggregory $ + * @since 1.4 + */ +public class StringUtils { + + /** + *

+ * Compares two CharSequences, returning true if they represent equal sequences of characters. + *

+ * + *

+ * nulls are handled without exceptions. Two null references are considered to be equal. + * The comparison is case sensitive. + *

+ * + *
+     * StringUtils.equals(null, null)   = true
+     * StringUtils.equals(null, "abc")  = false
+     * StringUtils.equals("abc", null)  = false
+     * StringUtils.equals("abc", "abc") = true
+     * StringUtils.equals("abc", "ABC") = false
+     * 
+ * + *

+ * Copied from Apache Commons Lang r1583482 on April 10, 2014 (day of 3.3.2 release). + *

+ * + * @see Object#equals(Object) + * @param cs1 + * the first CharSequence, may be null + * @param cs2 + * the second CharSequence, may be null + * @return true if the CharSequences are equal (case-sensitive), or both null + * @since 1.10 + */ + public static boolean equals( final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1 instanceof String && cs2 instanceof String ) { + return cs1.equals(cs2); + } + return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length())); + } + + /** + * Calls {@link String#getBytes(Charset)} + * + * @param string + * The string to encode (if null, return null). + * @param charset + * The {@link Charset} to encode the String + * @return the encoded bytes + */ + private static byte[] getBytes( final String string, final Charset charset) { + if (string == null) { + return null; + } + return string.getBytes(charset); + } + + /** + * Encodes the given string into a sequence of bytes using the ISO-8859-1 charset, storing the result into a new + * byte array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesIso8859_1(final String string) { + return getBytes(string, Charsets.ISO_8859_1); + } + + + /** + * Encodes the given string into a sequence of bytes using the named charset, storing the result into a new byte + * array. + *

+ * This method catches {@link UnsupportedEncodingException} and rethrows it as {@link IllegalStateException}, which + * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. + *

+ * + * @param string + * the String to encode, may be null + * @param charsetName + * The name of a required {@link Charset} + * @return encoded bytes, or null if the input string was null + * @throws IllegalStateException + * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a + * required charset name. + * @see CharEncoding + * @see String#getBytes(String) + */ + public static byte[] getBytesUnchecked( final String string, final String charsetName) { + if (string == null) { + return null; + } + try { + return string.getBytes(charsetName); + } catch (final UnsupportedEncodingException e) { + throw StringUtils.newIllegalStateException(charsetName, e); + } + } + + /** + * Encodes the given string into a sequence of bytes using the US-ASCII charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUsAscii(final String string) { + return getBytes(string, Charsets.US_ASCII); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-16 charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf16(final String string) { + return getBytes(string, Charsets.UTF_16); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-16BE charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf16Be(final String string) { + return getBytes(string, Charsets.UTF_16BE); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-16LE charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf16Le(final String string) { + return getBytes(string, Charsets.UTF_16LE); + } + + /** + * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte + * array. + * + * @param string + * the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard charsets + * @see #getBytesUnchecked(String, String) + */ + public static byte[] getBytesUtf8(final String string) { + return getBytes(string, Charsets.UTF_8); + } + + private static IllegalStateException newIllegalStateException( final String charsetName, + final UnsupportedEncodingException e) { + return new IllegalStateException(charsetName + ": " + e); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the given charset. + * + * @param bytes + * The bytes to be decoded into characters + * @param charset + * The {@link Charset} to encode the String + * @return A new String decoded from the specified array of bytes using the given charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is + * required by the Java platform specification. + */ + private static String newString( final byte[] bytes, final Charset charset) { + return bytes == null ? null : new String(bytes, charset); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the given charset. + *

+ * This method catches {@link UnsupportedEncodingException} and re-throws it as {@link IllegalStateException}, which + * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. + *

+ * + * @param bytes + * The bytes to be decoded into characters, may be null + * @param charsetName + * The name of a required {@link Charset} + * @return A new String decoded from the specified array of bytes using the given charset, + * or null if the input byte array was null. + * @throws IllegalStateException + * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a + * required charset name. + * @see CharEncoding + * @see String#String(byte[], String) + */ + public static String newString( final byte[] bytes, final String charsetName) { + if (bytes == null) { + return null; + } + try { + return new String(bytes, charsetName); + } catch (final UnsupportedEncodingException e) { + throw StringUtils.newIllegalStateException(charsetName, e); + } + } + + /** + * Constructs a new String by decoding the specified array of bytes using the ISO-8859-1 charset. + * + * @param bytes + * The bytes to be decoded into characters, may be null + * @return A new String decoded from the specified array of bytes using the ISO-8859-1 charset, or + * null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringIso8859_1( final byte[] bytes) { + return new String(bytes, Charsets.ISO_8859_1); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the US-ASCII charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the US-ASCII charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUsAscii( final byte[] bytes) { + return new String(bytes, Charsets.US_ASCII); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-16 charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-16 charset + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf16( final byte[] bytes) { + return new String(bytes, Charsets.UTF_16); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-16BE charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-16BE charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf16Be( final byte[] bytes) { + return new String(bytes, Charsets.UTF_16BE); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-16LE charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-16LE charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf16Le( final byte[] bytes) { + return new String(bytes, Charsets.UTF_16LE); + } + + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-8 charset. + * + * @param bytes + * The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-8 charset, + * or null if the input byte array was null. + * @throws NullPointerException + * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is + * required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf8( final byte[] bytes) { + return newString(bytes, Charsets.UTF_8); + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/glide/BaseGlideModule.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/glide/BaseGlideModule.java new file mode 100644 index 0000000000..b77e27d37f --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/glide/BaseGlideModule.java @@ -0,0 +1,38 @@ +package com.mogo.utils.glide; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory; +import com.bumptech.glide.load.engine.cache.LruResourceCache; +import com.bumptech.glide.load.engine.cache.MemoryCache; +import com.bumptech.glide.module.AppGlideModule; + +/** + * Created by congtaowang on 2018/12/17. + */ +@GlideModule +public class BaseGlideModule extends AppGlideModule { + + public static final int MEMORY_CACHE_SIZE = 5 * 1024 * 1024; + public static final int DISK_CACHE_SIZE = 50 * 1024 * 1024; + public static final String DISK_CACHE_NAME = "glide"; + + @Override + public void applyOptions( Context context, GlideBuilder builder ) { + super.applyOptions( context, builder ); + /** + * 更改缓存最总文件夹名称 + * + * 是在sdcard/Android/data/包名/cache/DISK_CACHE_NAME目录当中 + */ + builder.setMemoryCache( new LruResourceCache( MEMORY_CACHE_SIZE ) ); + builder.setDiskCache( new ExternalPreferredCacheDiskCacheFactory( context, DISK_CACHE_NAME, DISK_CACHE_SIZE ) ); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java new file mode 100644 index 0000000000..1296d3a052 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java @@ -0,0 +1,24 @@ + +package com.mogo.utils.logger; + +public enum LogLevel { + + OFF( Integer.MAX_VALUE), + + VERBOSE(1), + + DEBUG(2), + + INFO(3), + + WARN(4), + + ERROR(5); + + public final int level; + + private LogLevel(final int level) { + this.level = level; + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java new file mode 100644 index 0000000000..152779902b --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java @@ -0,0 +1,57 @@ + +package com.mogo.utils.logger; + +public final class Logger { + private static final Printer sPrinter = new LoggerPrinter(); + + private Logger() { + } + + public static Settings init() { + return sPrinter.init(LogLevel.DEBUG); + } + + public static Settings init(LogLevel logLevel) { + return sPrinter.init(logLevel); + } + + public static void d( String tag, String message, Object... args) { + if(isLoggable(LogLevel.DEBUG)) sPrinter.d(tag, message, args); + } + + public static void e( String tag, String message, Object... args) { + if(isLoggable(LogLevel.ERROR)) sPrinter.e(tag, null, message, args); + } + + public static void e( String tag, Throwable throwable, String message, Object... args) { + if(isLoggable(LogLevel.ERROR)) sPrinter.e(tag, throwable, message, args); + } + + public static void i( String tag, String message, Object... args) { + if(isLoggable(LogLevel.INFO)) sPrinter.i(tag, message, args); + } + + public static void v( String tag, String message, Object... args) { + if(isLoggable(LogLevel.VERBOSE)) sPrinter.v(tag, message, args); + } + + public static void w( String tag, String message, Object... args) { + if(isLoggable(LogLevel.WARN)) sPrinter.w(tag, message, args); + } + + public static void easyLog( String tag, String message) { + if(isLoggable(LogLevel.DEBUG)) sPrinter.d(tag, message); + } + + public static void json( String tag, String json) { + if(isLoggable(LogLevel.DEBUG)) sPrinter.json(tag, json); + } + + public static void xml( String tag, String xml) { + if(isLoggable(LogLevel.DEBUG)) sPrinter.xml(tag, xml); + } + + private static boolean isLoggable(LogLevel logLevel){ + return sPrinter.getSettings().getLogLevel().level <= logLevel.level; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java new file mode 100644 index 0000000000..d4b65ba28c --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java @@ -0,0 +1,261 @@ + +package com.mogo.utils.logger; + +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +final class LoggerPrinter implements Printer { + private static final String TAG = "LoggerPrinter"; + + private static final int CHUNK_SIZE = 4000; + private static final int JSON_INDENT = 4; + private static final int MIN_STACK_OFFSET = 3; + private static final char TOP_LEFT_CORNER = '╔'; + private static final char BOTTOM_LEFT_CORNER = '╚'; + private static final char MIDDLE_CORNER = '╟'; + private static final char HORIZONTAL_DOUBLE_LINE = '║'; + private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════"; + private static final String SINGLE_DIVIDER = "────────────────────────────────────────────"; + private static final String TOP_BORDER = "╔════════════════════════════════════════════════════════════════════════════════════════"; + private static final String BOTTOM_BORDER = "╚════════════════════════════════════════════════════════════════════════════════════════"; + private static final String MIDDLE_BORDER = "╟────────────────────────────────────────────────────────────────────────────────────────"; + + private final Settings mSettings = new Settings(); + + LoggerPrinter() { + } + + public Settings init(LogLevel logLevel) { + return mSettings.setLogLevel(logLevel); + } + + public Settings getSettings() { + return mSettings; + } + + public void d( String tag, String message, Object... args) { + this.log(tag, LogLevel.DEBUG, message, args); + } + + public void e( String tag, String message, Object... args) { + this.e(tag, null, message, args); + } + + public void e( String tag, Throwable throwable, String message, Object... args) { + if (throwable != null && message != null) { + message = message + " : " + throwable.toString(); + } + + if (throwable != null && message == null) { + message = throwable.toString(); + } + + if (message == null) { + message = "No message/exception is set"; + } + + this.log(tag, LogLevel.ERROR, message, args); + } + + public void w( String tag, String message, Object... args) { + this.log(tag, LogLevel.WARN, message, args); + } + + public void i( String tag, String message, Object... args) { + this.log(tag, LogLevel.INFO, message, args); + } + + public void v( String tag, String message, Object... args) { + this.log(tag, LogLevel.VERBOSE, message, args); + } + + public void json( String tag, String json) { + if ( TextUtils.isEmpty(json)) { + this.d(tag, "Empty/Null json content"); + } else { + try { + String message; + if (json.startsWith("{")) { + JSONObject e1 = new JSONObject(json); + message = e1.toString(4); + this.d(tag, message); + return; + } + + if (json.startsWith("[")) { + JSONArray e = new JSONArray(json); + message = e.toString(4); + this.d(tag, message); + } + } catch ( JSONException var4) { + this.e(tag, var4.getCause().getMessage() + "\n" + json); + } + + } + } + + public void xml( String tag, String xml) { + if ( TextUtils.isEmpty(xml)) { + this.d(tag, "Empty/Null xml content"); + } else { + try { + StreamSource e = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty("indent", "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(e, xmlOutput); + this.d(tag, xmlOutput.getWriter().toString().replaceFirst(">", ">\n")); + } catch ( TransformerException var5) { + this.e(tag, var5.getCause().getMessage() + "\n" + xml); + } + + } + } + + public void normalLog( String tag, String message) { + if (!TextUtils.isEmpty(message)) { + this.logChunk(LogLevel.DEBUG, tag, message); + } + } + + private synchronized void log( String tag, LogLevel logLevel, String msg, Object... args) { + String message = this.createMessage(msg, args); + int methodCount = this.getMethodCount(); + this.logTopBorder(logLevel, tag); + this.logHeaderContent(logLevel, tag, methodCount); + byte[] bytes = message.getBytes(); + int length = bytes.length; + if (length <= 4000) { + if (methodCount > 0) { + this.logDivider(logLevel, tag); + } + + this.logContent(logLevel, tag, message); + this.logBottomBorder(logLevel, tag); + } else { + if (methodCount > 0) { + this.logDivider(logLevel, tag); + } + + for (int i = 0; i < length; i += 4000) { + int count = Math.min(length - i, 4000); + this.logContent(logLevel, tag, new String(bytes, i, count)); + } + + this.logBottomBorder(logLevel, tag); + } + } + + private void logTopBorder(LogLevel logLevel, String tag) { + this.logChunk(logLevel, tag, "╔════════════════════════════════════════════════════════════════════════════════════════"); + } + + private void logHeaderContent( LogLevel logLevel, String tag, int methodCount) { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + if (mSettings.isShowThreadInfo()) { + this.logChunk(logLevel, tag, "║ Thread: " + Thread.currentThread().getName()); + this.logDivider(logLevel, tag); + } + + String level = ""; + int stackOffset = this.getStackOffset(trace) + mSettings.getMethodOffset(); + if (methodCount + stackOffset > trace.length) { + methodCount = trace.length - stackOffset - 1; + } + + for (int i = methodCount; i > 0; --i) { + int stackIndex = i + stackOffset; + if (stackIndex < trace.length) { + StringBuilder builder = new StringBuilder(); + builder.append("║ ").append(level).append(this.getSimpleClassName(trace[stackIndex].getClassName())).append(".").append(trace[stackIndex].getMethodName()).append(" ").append(" (").append(trace[stackIndex].getFileName()).append(":").append(trace[stackIndex].getLineNumber()).append(")"); + level = level + " "; + this.logChunk(logLevel, tag, builder.toString()); + } + } + + } + + private void logBottomBorder(LogLevel logLevel, String tag) { + this.logChunk(logLevel, tag, "╚════════════════════════════════════════════════════════════════════════════════════════"); + } + + private void logDivider(LogLevel logLevel, String tag) { + this.logChunk(logLevel, tag, "╟────────────────────────────────────────────────────────────────────────────────────────"); + } + + private void logContent( LogLevel logLevel, String tag, String chunk) { + String[] lines = chunk.split( System.getProperty("line.separator")); + + for ( String line : lines) { + this.logChunk(logLevel, tag, "║ " + line); + } + } + + private void logChunk( LogLevel logLevel, String tag, String chunk) { + String finalTag = this.checkTag(tag); + switch (logLevel) { + case VERBOSE: + Log.v(finalTag, chunk); + break; + case INFO: + Log.i(finalTag, chunk); + break; + case DEBUG: + Log.d(finalTag, chunk); + break; + case WARN: + Log.w(finalTag, chunk); + break; + case ERROR: + Log.e(finalTag, chunk); + break; + case OFF: + break; + } + + } + + private String getSimpleClassName( String name) { + int lastIndex = name.lastIndexOf("."); + return name.substring(lastIndex + 1); + } + + private String checkTag( String tag) { + return TextUtils.isEmpty(tag) ? TAG : tag; + } + + private String createMessage( String message, Object... args) { + return (args == null || args.length == 0) ? message : String.format(message, args); + } + + private int getMethodCount() { + return mSettings.getMethodCount(); + } + + private int getStackOffset( StackTraceElement[] trace) { + for (int i = 3; i < trace.length; ++i) { + StackTraceElement e = trace[i]; + String name = e.getClassName(); + if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(Logger.class.getName())) { + --i; + return i; + } + } + + return -1; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java new file mode 100644 index 0000000000..57def529a6 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java @@ -0,0 +1,26 @@ + +package com.mogo.utils.logger; + +public interface Printer { + Settings init( LogLevel logLevel ); + + Settings getSettings(); + + void d( String tag, String message, Object... args ); + + void e( String tag, String message, Object... args ); + + void e( String tag, Throwable throwable, String message, Object... args ); + + void w( String tag, String message, Object... args ); + + void i( String tag, String message, Object... args ); + + void v( String tag, String message, Object... args ); + + void json( String tag, String json ); + + void xml( String tag, String xml ); + + void normalLog( String tag, String message ); +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java new file mode 100644 index 0000000000..af6ec2213b --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java @@ -0,0 +1,52 @@ + +package com.mogo.utils.logger; + +public final class Settings { + private int methodCount = 2; + private boolean showThreadInfo = true; + private int methodOffset = 0; + private LogLevel logLevel = LogLevel.DEBUG; + + public Settings() { + } + + public Settings hideThreadInfo() { + this.showThreadInfo = false; + return this; + } + + public Settings setMethodCount(int methodCount) { + if(methodCount < 0) { + methodCount = 0; + } + + this.methodCount = methodCount; + return this; + } + + public Settings setLogLevel(LogLevel logLevel) { + this.logLevel = logLevel; + return this; + } + + public Settings setMethodOffset(int offset) { + this.methodOffset = offset; + return this; + } + + public int getMethodCount() { + return this.methodCount; + } + + public boolean isShowThreadInfo() { + return this.showThreadInfo; + } + + public LogLevel getLogLevel() { + return this.logLevel; + } + + public int getMethodOffset() { + return this.methodOffset; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/BaseParams.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/BaseParams.java new file mode 100644 index 0000000000..349fce9496 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/BaseParams.java @@ -0,0 +1,179 @@ +package com.mogo.utils.network; + +import android.text.TextUtils; + +import com.mogo.utils.logger.Logger; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public abstract class BaseParams { + + private static final String TAG = "BaseParams"; + + protected ConcurrentHashMap< String, Object > urlParams; + + protected BaseParams() { + this.init(); + } + + public Object get( String key ) { + return this.urlParams != null && key != null ? this.urlParams.get( key ) : ""; + } + + protected BaseParams( Map< String, Object > source ) { + this.init(); + Iterator iterator = source.entrySet().iterator(); + + while ( iterator.hasNext() ) { + Map.Entry entry = ( Map.Entry ) iterator.next(); + this.put( ( String ) entry.getKey(), entry.getValue() ); + } + + } + + protected BaseParams( String key, Object value ) { + this.init(); + this.put( key, value ); + } + + protected BaseParams( Object... keysAndValues ) { + this.init(); + int len = keysAndValues.length; + if ( len % 2 != 0 ) { + throw new IllegalArgumentException( "Supplied arguments must be even" ); + } else { + for ( int i = 0; i < len; i += 2 ) { + String key = String.valueOf( keysAndValues[i] ); + Object value = keysAndValues[i + 1]; + this.put( key, value ); + } + + } + } + + private boolean checkValue( final Object value ) { + if ( value instanceof CharSequence ) { + return !TextUtils.isEmpty( ( CharSequence ) value ); + } + + if ( value == null || value.getClass() == null ) { + return false; + } + + final Class< ? > clazz = value.getClass(); + return clazz.isPrimitive() || + clazz.isAssignableFrom( Boolean.class ) || + clazz.isAssignableFrom( Character.class ) || + clazz.isAssignableFrom( Byte.class ) || + clazz.isAssignableFrom( Short.class ) || + clazz.isAssignableFrom( Integer.class ) || + clazz.isAssignableFrom( Long.class ) || + clazz.isAssignableFrom( Float.class ) || + clazz.isAssignableFrom( Double.class ); + } + + private boolean checkKey( String key ) { + return !TextUtils.isEmpty( key ); + } + + public BaseParams put( String key, Object value ) { + if ( checkKey( key ) && checkValue( value ) ) { + urlParams.put( key, value ); + } else { + Logger.e( TAG, "parameter key is illegal or parameter value is illegal" ); + } + return this; + } + + public Object remove( String key ) { + return this.urlParams.remove( key ); + } + + public String toString() { + StringBuilder result = new StringBuilder(); + Iterator< Map.Entry< String, Object > > iterator = this.urlParams.entrySet().iterator(); + while ( iterator.hasNext() ) { + Map.Entry< String, Object > entry = iterator.next(); + if ( result.length() > 0 ) { + result.append( "&" ); + } + + result.append( entry.getKey() ); + result.append( "=" ); + result.append( entry.getValue() ); + } + return result.toString(); + } + + private void init() { + this.urlParams = new ConcurrentHashMap< String, Object >(); + } + + public List< BasicNameValuePair > getParamsList() { + LinkedList< BasicNameValuePair > pairs = new LinkedList< BasicNameValuePair >(); + Iterator< Map.Entry< String, Object > > iterator = this.urlParams.entrySet().iterator(); + + while ( iterator.hasNext() ) { + Map.Entry< String, Object > entry = iterator.next(); + pairs.add( new BasicNameValuePair( entry.getKey(), entry.getValue() ) ); + } + + return pairs; + } + + public Map< String, Object > getOriginMap() { + return urlParams; + } + + public String getParamString() { + StringBuilder result = new StringBuilder(); + Iterator< BasicNameValuePair > iterator = getParamsList().iterator(); + + while ( iterator.hasNext() ) { + BasicNameValuePair parameter = iterator.next(); + String key = parameter.getName(); + Object value = parameter.getValue(); + if ( result.length() > 0 ) { + result.append( "&" ); + } + + result.append( key ); + result.append( "=" ); + result.append( value ); + } + + return result.toString(); + } + + public static class BasicNameValuePair { + private String name; + private Object value; + + public BasicNameValuePair( String name, Object value ) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public void setName( String name ) { + this.name = name; + } + + public Object getValue() { + return value; + } + + public void setValue( Object value ) { + this.value = value; + } + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerNotAliveException.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerNotAliveException.java new file mode 100644 index 0000000000..57d61834c7 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerNotAliveException.java @@ -0,0 +1,10 @@ +package com.mogo.utils.network; + + + +public class CallerNotAliveException extends Exception { + + public CallerNotAliveException( String message){ + super(message); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerRestrictTo.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerRestrictTo.java new file mode 100644 index 0000000000..1818e103ae --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerRestrictTo.java @@ -0,0 +1,13 @@ +package com.mogo.utils.network; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Retention( RetentionPolicy.SOURCE) +@Target( ElementType.ANNOTATION_TYPE) +public @interface CallerRestrictTo { + Class[] value(); +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerType.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerType.java new file mode 100644 index 0000000000..facb8aea86 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/CallerType.java @@ -0,0 +1,29 @@ +package com.mogo.utils.network; + +import android.app.Activity; +import android.app.Dialog; +import android.app.Fragment; +import android.content.ContextWrapper; +import android.view.View; +import android.widget.PopupWindow; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Retention( RetentionPolicy.SOURCE ) +@Target( ElementType.PARAMETER ) +@CallerRestrictTo( { + Activity.class, + Fragment.class, + androidx.fragment.app.Fragment.class, + View.class, + Dialog.class, + PopupWindow.class, + ContextWrapper.class, + Object.class +} ) +public @interface CallerType { +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/HttpParams.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/HttpParams.java new file mode 100644 index 0000000000..0937b36c8c --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/HttpParams.java @@ -0,0 +1,70 @@ +package com.mogo.utils.network; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpParams extends BaseParams { + public HttpParams() { + } + + public HttpParams( Map< String, Object > source) { + super(source); + } + + public HttpParams( Object... keysAndValues) { + super(keysAndValues); + } + + public HttpParams( String key, Object value) { + super(key, value); + } + + public List getParamList() { + List list = getParamsList(); + Collections.sort(list, new KVPairComparator()); + return list; + } + + public String getSortedParamsString() { + List list = getParamList(); + StringBuilder sb = new StringBuilder(NetConfig.instance().getSignaturePrefix()); + for (BasicNameValuePair basicNameValuePair : list) { + sb.append(basicNameValuePair.getName()); + sb.append(basicNameValuePair.getValue()); + } + return sb.toString(); + } + + public Map< String, Object > getParamsMap() { + List list = getParamList(); + HashMap< String, Object > map = new HashMap< String, Object >(); + for (BasicNameValuePair basicNameValuePair : list) { + map.put(basicNameValuePair.getName(), basicNameValuePair.getValue()); + } + return map; + } + + public String getSortedUrlParamsString() { + List list = getParamList(); + StringBuilder sb = new StringBuilder(); + for (BasicNameValuePair basicNameValuePair : list) { + String key = basicNameValuePair.getName(); + Object value = basicNameValuePair.getValue(); + if (sb.length() > 0) + sb.append("&"); + sb.append(key); + sb.append("="); + sb.append(value); + } + return sb.toString(); + } + + private class KVPairComparator implements Comparator { + public int compare(BasicNameValuePair pairA, BasicNameValuePair pairB) { + return pairA.getName().compareTo(pairB.getName()); + } + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConfig.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConfig.java new file mode 100644 index 0000000000..a0fe0af85f --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConfig.java @@ -0,0 +1,160 @@ +package com.mogo.utils.network; + +import android.app.Application; +import android.content.Context; + +import androidx.collection.ArraySet; + +import java.util.Map; +import java.util.Set; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; + +import okhttp3.Interceptor; +import okhttp3.internal.tls.OkHostnameVerifier; + +/** + *

+ * This is a configuration class provided for clients.For better extension and customization,client can set customized parameters of network + * based on their own application. However you should set these parameters before you get the global instance + * of {@link okhttp3.OkHttpClient} by {@link OkHttpFactory} for the first time.

+ *

+ * Some of the parameters have default values, so there is not a must to use this class.

+ */ + +public final class NetConfig { + private long readTimeout = NetConstants.READ_TIMEOUT; + private long writeTimeout = NetConstants.WRITE_TIMEOUT; + private long connectTimeout = NetConstants.CONNECT_TIMEOUT; + + private final Set< Interceptor > interceptors = new ArraySet<>(); + private final Set< Interceptor > networkInterceptors = new ArraySet<>(); + private final HostnameVerifier allowAllHostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify( String hostname, SSLSession session ) { + return true; + } + }; + + private String signaturePrefix = "com.foundation.network"; + private HostnameVerifier hostnameVerifier = OkHostnameVerifier.INSTANCE; + private Map< String, Object > publicParams; + private boolean isLoggable; + private Context appContext; + + private SSLContext sslContext; + + private NetConfig() { + } + + private static final class SingletonHolder { + private static final NetConfig INSTANCE = new NetConfig(); + } + + public static NetConfig instance() { + return SingletonHolder.INSTANCE; + } + + public synchronized long getReadTimeout() { + return readTimeout; + } + + public synchronized NetConfig setReadTimeout( int readTimeout ) { + this.readTimeout = readTimeout; + return this; + } + + public synchronized long getWriteTimeout() { + return writeTimeout; + } + + public synchronized NetConfig setWriteTimeout( int writeTimeout ) { + this.writeTimeout = writeTimeout; + return this; + } + + public synchronized long getConnectTimeout() { + return connectTimeout; + } + + public synchronized NetConfig setConnectTimeout( int connectTimeout ) { + this.connectTimeout = connectTimeout; + return this; + } + + public synchronized HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public synchronized NetConfig setHostnameVerifier( HostnameVerifier hostnameVerifier ) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + + public synchronized NetConfig setSignaturePrefix( String prefix ) { + this.signaturePrefix = prefix; + return this; + } + + public synchronized String getSignaturePrefix() { + return signaturePrefix; + } + + public synchronized Set< Interceptor > getInterceptors() { + return interceptors; + } + + public synchronized NetConfig addInterceptor( Interceptor interceptor ) { + interceptors.add( interceptor ); + return this; + } + + public synchronized Set< Interceptor > getNetworkInterceptors() { + return networkInterceptors; + } + + public synchronized NetConfig addNetworkInterceptor( Interceptor networkInterceptor ) { + networkInterceptors.add( networkInterceptor ); + return this; + } + + public synchronized NetConfig setPublicParams( Map< String, Object > publicParams ) { + this.publicParams = publicParams; + return this; + } + + public synchronized Map< String, Object > getPublicParams() { + return publicParams; + } + + public synchronized boolean isLoggable() { + return isLoggable; + } + + public synchronized void setLoggable( boolean loggable ) { + isLoggable = loggable; + } + + public synchronized NetConfig setAppContext( Context appContext ) { + if ( appContext instanceof Application ) { + this.appContext = appContext; + } else { + this.appContext = appContext.getApplicationContext(); + } + return this; + } + + public synchronized Context getAppContext() { + return appContext; + } + + public synchronized SSLContext getSslContext() { + return sslContext; + } + + public synchronized void setSslContext( SSLContext sslContext ) { + this.sslContext = sslContext; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConstants.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConstants.java new file mode 100644 index 0000000000..2ef1cd3a02 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetConstants.java @@ -0,0 +1,13 @@ +package com.mogo.utils.network; + + +public class NetConstants { + /** 无数据 */ + public final static int NO_DATA = -800; + /** 数据返回正常 */ + public final static int OK = 0; + + public static final long READ_TIMEOUT = 20_000L; + public static final long WRITE_TIMEOUT = 20_000L; + public static final long CONNECT_TIMEOUT = 15_000L; +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkMonitorInterceptor.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkMonitorInterceptor.java new file mode 100644 index 0000000000..a014b352b4 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkMonitorInterceptor.java @@ -0,0 +1,24 @@ +package com.mogo.utils.network; + +import android.content.Context; + +import com.mogo.utils.CheckUtils; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Response; + + +final class NetworkMonitorInterceptor implements Interceptor { + private static final String TAG = "NetworkMonitorInterceptor"; + + @Override + public Response intercept(Chain chain) throws IOException { + final Context appContext = NetConfig.instance().getAppContext(); + if(appContext != null && !CheckUtils.isNetworkConnected(appContext)){ + throw new NetworkUnavailableException("Network is unavailable"); + } + return chain.proceed(chain.request()); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkUnavailableException.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkUnavailableException.java new file mode 100644 index 0000000000..425d14c6e4 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/NetworkUnavailableException.java @@ -0,0 +1,18 @@ +package com.mogo.utils.network; + +import java.io.IOException; + +import okhttp3.Interceptor; + +/** + * An exception occurs whenever network is not reachable, it's threw by {@link NetworkMonitorInterceptor} + * during {@link NetworkMonitorInterceptor#intercept(Interceptor.Chain)} method. + * + */ + +public class NetworkUnavailableException extends IOException { + + public NetworkUnavailableException( String message) { + super(message); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/OkHttpFactory.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/OkHttpFactory.java new file mode 100644 index 0000000000..02e05d5592 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/OkHttpFactory.java @@ -0,0 +1,55 @@ +package com.mogo.utils.network; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; + +/** + *

+ * Before getting an global instance of {@link OkHttpClient}, you can use {@link NetConfig} to set some + * parameters of network. Note that network configuration must be first set before you get an instance of {@link OkHttpClient}, + * for the first time, otherwise configuration is invalid

+ * + */ +public final class OkHttpFactory { + + private static volatile OkHttpClient sInstance; + + private OkHttpFactory() { + } + + public static OkHttpClient getInstance() { + if (sInstance == null) { + synchronized (OkHttpClient.class) { + if (sInstance == null) { + final OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.addInterceptor(new NetworkMonitorInterceptor()); + builder.addInterceptor(new RequestLogInterceptor()); + builder.addInterceptor(new ResponseLogInterceptor()); + builder.hostnameVerifier(NetConfig.instance().getHostnameVerifier()); + if ( NetConfig.instance().getSslContext() != null ) { + builder.sslSocketFactory( NetConfig.instance().getSslContext().getSocketFactory()); + } + builder.connectTimeout(NetConfig.instance().getConnectTimeout(), TimeUnit.MILLISECONDS); + builder.readTimeout(NetConfig.instance().getReadTimeout(), TimeUnit.MILLISECONDS); + builder.writeTimeout(NetConfig.instance().getWriteTimeout(), TimeUnit.MILLISECONDS); + final Set interceptors = NetConfig.instance().getInterceptors(); + for(Interceptor interceptor : interceptors){ + builder.addInterceptor(interceptor); + } + + final Set networkInterceptors = NetConfig.instance().getNetworkInterceptors(); + for(Interceptor networkInterceptor : networkInterceptors){ + builder.addNetworkInterceptor(networkInterceptor); + } + + sInstance = builder.build(); + } + } + } + + return sInstance; + } +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestLogInterceptor.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestLogInterceptor.java new file mode 100644 index 0000000000..5af287c703 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestLogInterceptor.java @@ -0,0 +1,46 @@ +package com.mogo.utils.network; + +import com.mogo.utils.logger.Logger; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + + +final class RequestLogInterceptor implements Interceptor { + private static final String TAG = "RequestLogInterceptor"; + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + RequestBody requestBody = request.body(); + boolean hasRequestBody = requestBody != null; + + String protocol = Protocol.HTTP_1_1.toString(); + if(chain.connection() != null && chain.connection().protocol() != null){ + protocol = chain.connection().protocol().toString(); + } + + StringBuilder logMsg = new StringBuilder(); + logMsg.append("--> "); + logMsg.append(protocol).append(", "); + logMsg.append(request.method()).append(", "); + logMsg.append("Request Headers: ").append(request.headers()).append("\r\n"); + logMsg.append(request.url()).append("\r\n"); + if(hasRequestBody){ + logMsg.append("Content-Type: ").append(requestBody.contentType()).append(", "); + logMsg.append("Content-Length: ").append(requestBody.contentLength()); + } + logMsg.append(" <-- end http request"); + + if(NetConfig.instance().isLoggable()){ + Logger.d(TAG, logMsg.toString()); + } + + return chain.proceed(request); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestOptions.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestOptions.java new file mode 100644 index 0000000000..d0bae21535 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RequestOptions.java @@ -0,0 +1,127 @@ +package com.mogo.utils.network; + +import android.content.Context; +import android.view.KeyEvent; + + +import com.mogo.utils.network.utils.Util; + +import java.util.Collections; +import java.util.Map; + + +public class RequestOptions { + private static final String DEFAULT_LOADING_MESSAGE = "正在加载中..."; + + private Object caller; + private Context context; + private Map< String, Object > parameter; + private CharSequence loadingMessage; + private boolean loading; + private boolean cancelable; + private boolean cancelableOnTouchOutside; + + /** + * Private constructor with some default initialization. + */ + private RequestOptions( @CallerType Object caller ) { + this.caller = caller; + this.context = Util.getContext( caller ); + this.parameter = Collections.emptyMap(); + this.loadingMessage = DEFAULT_LOADING_MESSAGE; + this.loading = false; + this.cancelable = true; + this.cancelableOnTouchOutside = false; + } + + /** + * Factory method to create an instance of {@link RequestOptions} + * + *

+ * Request caller can be any type of object. HOWEVER, if you want to show loading message + * when request is ongoing or if you want to monitor caller's lifecycle in process of requesting + * in order to avoid unexpected async exceptions, please use one of {@link android.app.Activity}, + * {@link android.app.Fragment},{@link androidx.fragment.app.Fragment},{@link android.view.View}, + * {@link android.app.Dialog} and {@link android.widget.PopupWindow} as caller

+ * + *

+ * Caller can be other types if loading message is not intended to be shown.

+ * + *

+ * It's strongly suggested that caller should have an context for better control of caller's lifecycle.

+ */ + public static RequestOptions create( Object caller ) { + return new RequestOptions( caller ); + } + + /** + * Mutator for request parameters + */ + public RequestOptions parameter( Map< String, Object > parameter ) { + this.parameter = parameter; + return this; + } + + /** + * Mutator for indicating whether loading message should be displayed while request is ongoing + */ + public RequestOptions loading( boolean loading ) { + this.loading = loading; + return this; + } + + /** + * Mutator for loading message + */ + public RequestOptions loadingMessage( CharSequence loadingMessage ) { + this.loading = true; + this.loadingMessage = loadingMessage; + return this; + } + + /** + * Mutator for indicating that loading dialog is cancelable with the {@link KeyEvent#KEYCODE_BACK BACK} key. + * Default value is true. + */ + public RequestOptions cancelable( boolean cancelable ) { + this.cancelable = cancelable; + return this; + } + + /** + * Mutator for indicating that loading dialog is cancelable with touching outside of the dialog. + * Default value is false. + */ + public RequestOptions cancelableOnTouchOutside( boolean cancelableOnTouchOutside ) { + this.cancelableOnTouchOutside = cancelableOnTouchOutside; + return this; + } + + public Object getCaller() { + return caller; + } + + public Context getContext() { + return context; + } + + public Map< String, Object > getParameter() { + return parameter; + } + + public CharSequence getLoadingMessage() { + return loadingMessage; + } + + public boolean isLoading() { + return loading; + } + + public boolean isCancelable() { + return cancelable; + } + + public boolean isCancelableOnTouchOutside() { + return cancelableOnTouchOutside; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ResponseLogInterceptor.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ResponseLogInterceptor.java new file mode 100644 index 0000000000..0199ebc717 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ResponseLogInterceptor.java @@ -0,0 +1,59 @@ +package com.mogo.utils.network; + + +import com.mogo.utils.logger.Logger; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Response; +import okhttp3.ResponseBody; + + +final class ResponseLogInterceptor implements Interceptor { + private static final String TAG = "ResponseLogInterceptor"; + + @Override + public Response intercept( Chain chain ) throws IOException { + long startTime = System.nanoTime(); + Response response = chain.proceed( chain.request() ); + long endTime = TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - startTime ); + + ResponseBody responseBody = response.body(); + String responseContent = null; + String bodySize = null; + MediaType contentType = null; + boolean consumedResponse = false; + + StringBuilder logMsg = new StringBuilder(); + + if ( responseBody != null ) { + long contentLength = responseBody.contentLength(); + bodySize = contentLength != -1L ? contentLength + "-byte" : "unknown-length"; + contentType = responseBody.contentType(); + responseContent = responseBody.string(); + consumedResponse = true; + } + + logMsg.append( "--> " ); + logMsg.append( response.code() ).append( " " ); + logMsg.append( response.message() ).append( " " ); + logMsg.append( response.protocol() ).append( " " ); + logMsg.append( response.request().url() ).append( "\r\n" ); + logMsg.append( "Response Content: " ).append( responseContent ).append( "\r\n" ); + logMsg.append( "Content-Type: " ).append( contentType ).append( ", " ); + logMsg.append( "Content-Length: " ).append( bodySize ).append( ", " ); + logMsg.append( " (" ).append( endTime ).append( "ms)" ); + logMsg.append( " <-- end http response" ); + + if ( NetConfig.instance().isLoggable() ) { + Logger.d( TAG, logMsg.toString() ); + } + + return consumedResponse ? + response.newBuilder().body( ResponseBody.create( contentType, responseContent ) ).build() : + response; + } +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RetrofitFactory.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RetrofitFactory.java new file mode 100644 index 0000000000..f26c1b1d74 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/RetrofitFactory.java @@ -0,0 +1,34 @@ +package com.mogo.utils.network; + + +import androidx.collection.ArrayMap; + +import java.util.Map; + +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +public final class RetrofitFactory { + + private static final Map< String, Retrofit> sRpcServiceMap = new ArrayMap< String,Retrofit>(); + + private RetrofitFactory(){} + + public static synchronized Retrofit getInstance( String baseUrl) { + Retrofit target = sRpcServiceMap.get(baseUrl); + + if(target == null){ + target = new Retrofit.Builder(). + client(OkHttpFactory.getInstance()). + baseUrl(baseUrl). + addCallAdapterFactory(RxJavaCallAdapterFactory.create()). + addConverterFactory(GsonConverterFactory.create()). + build(); + sRpcServiceMap.put(baseUrl,target); + } + + return target; + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ServerParam.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ServerParam.java new file mode 100644 index 0000000000..87a7d768d1 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ServerParam.java @@ -0,0 +1,5 @@ +package com.mogo.utils.network; + +public class ServerParam { + public static final String SIGNATURE = "sig"; +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/SubscriberEx.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/SubscriberEx.java new file mode 100644 index 0000000000..158c431852 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/SubscriberEx.java @@ -0,0 +1,95 @@ +package com.mogo.utils.network; + + +import androidx.annotation.CallSuper; + + +import com.mogo.utils.network.ui.ProgressDialog; +import com.mogo.utils.network.utils.Util; + +import rx.Subscriber; + + +/** + *

+ * Extension of {@link Subscriber}. For better extension and customization, client can extend this + * class to override the default behaviours such as loading appearance on each lifecycle method + * of network callback.

+ */ + +public abstract class SubscriberEx< T > extends Subscriber< T > { + protected final RequestOptions mRequestOptions; + private ProgressDialog mProgressDialog; + + public SubscriberEx( RequestOptions requestOptions ) { + if ( requestOptions == null ) { + throw new IllegalArgumentException( "RequestOptions cannot be null" ); + } + this.mRequestOptions = requestOptions; + if ( mRequestOptions.isLoading() ) { + this.mProgressDialog = new ProgressDialog(); + } + } + + /** + * This method must be called if you want to use the default loading dialog in case of override. + * Otherwise you can ignore it. + */ + @Override + @CallSuper + public void onStart() { + super.onStart(); + if ( !Util.checkAlive( mRequestOptions.getCaller() ) ) { + unsubscribe(); + return; + } + if ( mRequestOptions.isLoading() && mProgressDialog != null ) { + mProgressDialog.showLoadingDialog( mRequestOptions.getContext(), mRequestOptions.getLoadingMessage(), mRequestOptions.isCancelable(), mRequestOptions.isCancelableOnTouchOutside() ); + } + } + + /** + * This method must be called if you want to use the default loading dialog in case of override. + * Otherwise you can ignore it. + */ + private void onFinish() { + if ( mRequestOptions.isLoading() && mProgressDialog != null ) { + mProgressDialog.removeLoadingDialog(); + mProgressDialog = null; + } + if ( !Util.checkAlive( mRequestOptions.getCaller() ) ) { + unsubscribe(); + } + } + + /** + * This method must be called if you want to use the default loading dialog in case of override. + * Otherwise you can ignore it. + */ + @Override + @CallSuper + public void onCompleted() { + onFinish(); + } + + /** + * This method must be called if you want to use the default loading dialog in case of override. + * Otherwise you can ignore it. + */ + @Override + @CallSuper + public void onError( Throwable e ) { + onFinish(); + } + + /** + * This method must be override if you care about the result of request. + * + * @param o The result of network request + */ + @Override + @CallSuper + public void onNext( T o ) { + } + +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialog.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialog.java new file mode 100644 index 0000000000..ee5b507132 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialog.java @@ -0,0 +1,66 @@ +package com.mogo.utils.network.ui; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.mogo.utils.logger.Logger; + + +public class ProgressDialog { + + private static final String TAG = "ProgressDialog"; + + public static final String LOADING_DIALOG_TAG = "loading_dialog_tag"; + + private ProgressDialogFragment mProgressDialogFragment; + private FragmentManager mFragmentManager; + + public void showLoadingDialog( @NonNull Context context, CharSequence loadingMassage) { + showLoadingDialog(context, loadingMassage, true); + } + + public void showLoadingDialog( @NonNull Context context, CharSequence loadingMassage, boolean cancelable) { + showLoadingDialog(context, loadingMassage, cancelable, false); + } + + public void showLoadingDialog( Context context, CharSequence loadingMassage, boolean cancelable, boolean cancelableOnTouchOutSide) { + if (context != null && context instanceof FragmentActivity ) + mFragmentManager = ((FragmentActivity) context).getSupportFragmentManager(); + + if (mFragmentManager == null) + return; + + mProgressDialogFragment = new ProgressDialogFragment(); + mProgressDialogFragment.setContent(loadingMassage, cancelable, cancelableOnTouchOutSide); + + try { + FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); + fragmentTransaction.add( mProgressDialogFragment, LOADING_DIALOG_TAG ); + fragmentTransaction.add(mProgressDialogFragment, LOADING_DIALOG_TAG); + fragmentTransaction.commitAllowingStateLoss(); + } catch ( IllegalStateException e) { + Logger.e(TAG, e, e.getMessage()); + mFragmentManager = null; + mProgressDialogFragment = null; + } + } + + public void removeLoadingDialog() { + if (mProgressDialogFragment != null) { + try { + mProgressDialogFragment.dismissAllowingStateLoss(); + } catch ( Exception e) { + Logger.e(TAG, "dialog 取消异常"); + } finally { + mProgressDialogFragment = null; + mFragmentManager = null; + } + } + } + +} + diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialogFragment.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialogFragment.java new file mode 100644 index 0000000000..527957bf17 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/ui/ProgressDialogFragment.java @@ -0,0 +1,40 @@ +package com.mogo.utils.network.ui; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; + + +public class ProgressDialogFragment extends DialogFragment { + + /** + * Progress显示的文字 + */ + private CharSequence mMessage; + + private boolean mCancelableOnTouchOutSide; + + /** + * 设置dialog内容 + * + * @param message 显示的文字 + * @param cancelable 是否可以被取消 例如back键取消 + */ + public void setContent( CharSequence message, boolean cancelable, boolean cancelableOnTouchOutSide) { + mMessage = message; + mCancelableOnTouchOutSide = cancelableOnTouchOutSide; + setCancelable(cancelable); + } + + @NonNull + @Override + public Dialog onCreateDialog( Bundle savedInstanceState) { + ProgressDialog dialog = new ProgressDialog(getActivity()); + dialog.setMessage(mMessage); + dialog.setCanceledOnTouchOutside(mCancelableOnTouchOutSide); + return dialog; + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/GsonUtil.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/GsonUtil.java new file mode 100644 index 0000000000..ea1cfad71d --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/GsonUtil.java @@ -0,0 +1,77 @@ +package com.mogo.utils.network.utils; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + + +public class GsonUtil { + + private static volatile Gson gson; + + private GsonUtil() {} + + public static Gson getGson() { + if(gson == null){ + synchronized (GsonUtil.class) { + if (gson == null) { + GsonBuilder builder = new GsonBuilder(); + gson = builder.create(); + } + } + } + return gson; + } + + public static String jsonFromObject( Object object) { + if (object == null) { + return null; + } else { + try { + return getGson().toJson(object); + } catch ( Exception var2) { + return null; + } + } + } + + public static T objectFromJson( String json, Class klass) { + if (json == null) { + return null; + } else { + try { + return getGson().fromJson(json, klass); + } catch ( Exception var3) { + return null; + } + } + } + + + public static List arrayFromJson( String json, Class clazz) { + List list = new ArrayList(); + if ( TextUtils.isEmpty(json)) { + return null; + } + try { + org.json.JSONArray array = new org.json.JSONArray(json); + for (int i = 0; i < array.length(); i++) { + JSONObject object = array.getJSONObject(i); + String js = object.toString(); + T t = GsonUtil.objectFromJson(js, clazz); + list.add(t); + } + return list; + } catch ( Exception e) { + e.printStackTrace(); + } + return null; + + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/SignUtil.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/SignUtil.java new file mode 100644 index 0000000000..282bb3664e --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/SignUtil.java @@ -0,0 +1,120 @@ +package com.mogo.utils.network.utils; + + +import com.mogo.utils.digest.DigestUtils; +import com.mogo.utils.network.HttpParams; + +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + + +public class SignUtil { + + private static class StrSortTreeMap extends TreeMap< String, Object > { + private static final long serialVersionUID = 1L; + + public StrSortTreeMap() { + super(new Comparator< String >() { + + public int compare( String str1, String str2) { + return str1.compareTo(str2); + } + }); + } + } + + public static String createSign( Map< String, Object > map, String key) { + if (map == null || map.isEmpty()) { + return ""; + } + StrSortTreeMap treeMap = new StrSortTreeMap(); + for ( String k : map.keySet()) { + treeMap.put(k, map.get(k)); + } + return createSign(treeMap, key); + } + + private static String createSign( StrSortTreeMap map, String key) { + try{ + StringBuilder queryString = new StringBuilder(); + for ( Map.Entry< String, Object > entry : map.entrySet()) { + queryString.append(entry.getKey()) + .append("=") + .append(entry.getValue()) + .append("&"); + } + queryString.append("key=").append(DigestUtils.shaHex(key)); + return DigestUtils.shaHex(queryString.toString()).toUpperCase(); + }catch ( Exception e){ + e.printStackTrace(); + return ""; + } + } + + public static String encode( String string) { + try { + return URLEncoder.encode(string, "UTF-8"); + } catch ( UnsupportedEncodingException e) { + e.printStackTrace(); + return ""; + } + } + public static String decode( String string) { + try { + return URLDecoder.decode(string, "UTF-8"); + } catch ( UnsupportedEncodingException e) { + e.printStackTrace(); + return ""; + } + } + + /** + * 生成请求参数的签名 + * + * @param params HTTP请求中的所有POST或GET参数的Key-Value集合 + * @return 签名字符串,用于赋值给sig参数 + */ + public static String generateSignature( HttpParams params) { + String paramString = params.getSortedParamsString(); + try { + return DigestUtils.shaHex(paramString); + } catch ( Exception e) { + return ""; + } + } + + public static String generateSignature( Map< String, String > params) { + HttpParams httpParams = new HttpParams(params); + return generateSignature(httpParams); + } + + public static Map< String, Object > convertToMap( JSONObject jsonObject) { + final TreeMap< String, Object > sortedMap = new TreeMap< String, Object >(); + + if (jsonObject != null) { + Iterator< String > keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.opt(key); + sortedMap.put(key,value); + } + } + + return sortedMap; + } + + public static String signString( String value) { + try { + return DigestUtils.shaHex(value); + } catch ( Exception e) { + return ""; + } + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/Util.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/Util.java new file mode 100644 index 0000000000..9caf3d16cb --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/network/utils/Util.java @@ -0,0 +1,94 @@ +package com.mogo.utils.network.utils; + +import android.app.Activity; +import android.app.Dialog; +import android.app.Fragment; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Build; +import android.os.Looper; +import android.view.View; +import android.widget.PopupWindow; + +import com.mogo.utils.network.CallerNotAliveException; + +public class Util { + + public static boolean checkAlive( Object caller ) { + if ( caller instanceof Activity ) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? !( ( Activity ) caller ).isDestroyed() : !( ( Activity ) caller ).isFinishing(); + } else if ( caller instanceof Fragment ) { + return ( ( Fragment ) caller ).isAdded(); + } else if ( caller instanceof androidx.fragment.app.Fragment ) { + return ( ( androidx.fragment.app.Fragment ) caller ).isAdded(); + } else if ( caller instanceof View ) { + return true; + } else if ( caller instanceof Dialog ) { + return ( ( Dialog ) caller ).getWindow() != null; + } else if ( caller instanceof PopupWindow ) { + return ( ( PopupWindow ) caller ).getContentView() != null; + } + return caller != null; + } + + public static void assertCallerAlive( Object caller ) throws CallerNotAliveException { + if ( !checkAlive( caller ) ) { + throw new CallerNotAliveException( "Caller is not alive any more" ); + } + } + + public static Context getContext( Object object ) { + if ( object instanceof Activity ) { + return ( Activity ) object; + } else if ( object instanceof Fragment ) { + return ( ( Fragment ) object ).getActivity(); + } else if ( object instanceof androidx.fragment.app.Fragment ) { + return ( ( androidx.fragment.app.Fragment ) object ).getActivity(); + } else if ( object instanceof View ) { + return ( ( View ) object ).getContext(); + } else if ( object instanceof Dialog ) { + return ( ( Dialog ) object ).getContext(); + } else if ( object instanceof PopupWindow ) { + if ( ( ( PopupWindow ) object ).getContentView() != null ) + return ( ( PopupWindow ) object ).getContentView().getContext(); + else return null; + } else if ( object instanceof ContextWrapper ) { + return ( ( ContextWrapper ) object ).getBaseContext(); + } else { + return null; + } + } + + /** + * Throws an {@link IllegalArgumentException} if called on a thread other than the main + * thread. + */ + public static void assertMainThread() { + if ( !isOnMainThread() ) { + throw new IllegalArgumentException( "You must call this method on the main thread" ); + } + } + + /** + * Throws an {@link IllegalArgumentException} if called on the main thread. + */ + public static void assertBackgroundThread() { + if ( !isOnBackgroundThread() ) { + throw new IllegalArgumentException( "You must call this method on a background thread" ); + } + } + + /** + * Returns {@code true} if called on the main thread, {@code false} otherwise. + */ + public static boolean isOnMainThread() { + return Looper.myLooper() == Looper.getMainLooper(); + } + + /** + * Returns {@code true} if called on a background thread, {@code false} otherwise. + */ + public static boolean isOnBackgroundThread() { + return !isOnMainThread(); + } +} diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java new file mode 100644 index 0000000000..3acfd14fa3 --- /dev/null +++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java @@ -0,0 +1,131 @@ +package com.mogo.utils.storage; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.annotation.NonNull; + +public class SharedPrefsMgr { + + private static final String File_Name = "app_shared_pref"; + private static SharedPrefsMgr sInstance; + private static SharedPreferences sSharedPrefs; + + public synchronized static SharedPrefsMgr getInstance( @NonNull Context context ) { + if ( sInstance == null ) { + try { + sInstance = new SharedPrefsMgr( context.getApplicationContext() ); + } catch ( Exception e ) { + sInstance = new SharedPrefsMgr(); + } + } + return sInstance; + } + + private SharedPrefsMgr() { + + } + + private SharedPrefsMgr( Context context ) { + try { + sSharedPrefs = context.getSharedPreferences( File_Name, Context.MODE_PRIVATE ); + } catch ( Exception e ) { + e.printStackTrace(); + } + } + + public void putString( String key, String value ) { + try { + SharedPreferences.Editor editor = sSharedPrefs.edit(); + editor.putString( key, value ); + editor.apply(); + } catch ( Exception e ) { + } + } + + public String getString( String tag ) { + try { + return sSharedPrefs.getString( tag, "" ); + } catch ( Exception e ) { + return ""; + } + } + + public boolean getBoolean( String key, boolean defaultValue ) { + try { + return sSharedPrefs.getBoolean( key, defaultValue ); + } catch ( Exception e ) { + return defaultValue; + } + } + + public long getLong( String key, long defaultValue ) { + try { + return sSharedPrefs.getLong( key, defaultValue ); + } catch ( Exception e ) { + return defaultValue; + } + } + + public float getFloat( String key, float defaultValue ) { + try { + return sSharedPrefs.getFloat( key, defaultValue ); + } catch ( Exception e ) { + return defaultValue; + } + } + + public int getInt( String key, int value ) { + try { + return sSharedPrefs.getInt( key, value ); + } catch ( Exception e ) { + return value; + } + } + + public void putBoolean( String key, boolean value ) { + try { + SharedPreferences.Editor editor = sSharedPrefs.edit(); + editor.putBoolean( key, value ); + editor.apply(); + } catch ( Exception e ) { + } + } + + public void putLong( String key, long value ) { + try { + SharedPreferences.Editor editor = sSharedPrefs.edit(); + editor.putLong( key, value ); + editor.apply(); + } catch ( Exception e ) { + } + } + + public void putInt( String key, int value ) { + try { + SharedPreferences.Editor editor = sSharedPrefs.edit(); + editor.putInt( key, value ); + editor.apply(); + } catch ( Exception e ) { + } + } + + public void putFloat( String key, float value ) { + try { + SharedPreferences.Editor editor = sSharedPrefs.edit(); + editor.putFloat( key, value ); + editor.apply(); + } catch ( Exception e ) { + } + } + + public void remove( String key ) { + try { + SharedPreferences.Editor editor = sSharedPrefs.edit(); + editor.remove( key ); + editor.apply(); + } catch ( Exception e ) { + } + } + +} \ No newline at end of file diff --git a/foudations/mogo-utils/src/main/res/values/strings.xml b/foudations/mogo-utils/src/main/res/values/strings.xml new file mode 100644 index 0000000000..b136d6c475 --- /dev/null +++ b/foudations/mogo-utils/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + mogo-utils + diff --git a/foudations/mogo-utils/src/test/java/com/mogo/utils/ExampleUnitTest.java b/foudations/mogo-utils/src/test/java/com/mogo/utils/ExampleUnitTest.java new file mode 100644 index 0000000000..f20b6cb3c3 --- /dev/null +++ b/foudations/mogo-utils/src/test/java/com/mogo/utils/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.mogo.utils; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals( 4, 2 + 2 ); + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..199d16ede3 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..cdc2ce1117 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Dec 18 10:03:53 CST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..42417e852f --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not mQuery maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..e95643d6a2 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libraries/mogo-map/.gitignore b/libraries/mogo-map/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/libraries/mogo-map/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libraries/mogo-map/build.gradle b/libraries/mogo-map/build.gradle new file mode 100644 index 0000000000..545771335c --- /dev/null +++ b/libraries/mogo-map/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation rootProject.ext.dependencies.androidxappcompat + implementation rootProject.ext.dependencies.amapnavi3dmap + implementation rootProject.ext.dependencies.amapsearch + implementation rootProject.ext.dependencies.amaplocation + + implementation project(':foudations:mogo-utils') +} diff --git a/libraries/mogo-map/consumer-rules.pro b/libraries/mogo-map/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/mogo-map/proguard-rules.pro b/libraries/mogo-map/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/libraries/mogo-map/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/libraries/mogo-map/src/main/AndroidManifest.xml b/libraries/mogo-map/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..cfaf88eee3 --- /dev/null +++ b/libraries/mogo-map/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/ILifeCycle.java b/libraries/mogo-map/src/main/java/com/mogo/map/ILifeCycle.java new file mode 100644 index 0000000000..2752e1d112 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/ILifeCycle.java @@ -0,0 +1,25 @@ +package com.mogo.map; + +import android.os.Bundle; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 生命周期 + */ +public interface ILifeCycle { + + void onCreate( Bundle bundle ); + + void onResume(); + + void onPause(); + + void onDestroy(); + + void onSaveInstanceState( Bundle outState ); + + // mapview only. + void onLowMemory(); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/IMogoMap.java b/libraries/mogo-map/src/main/java/com/mogo/map/IMogoMap.java new file mode 100644 index 0000000000..5ff709f78b --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/IMogoMap.java @@ -0,0 +1,99 @@ +package com.mogo.map; + +import com.mogo.map.marker.IMogoMarker; +import com.mogo.map.marker.MogoMarkerOptions; + +import java.util.ArrayList; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图控制接口 + */ +public interface IMogoMap { + + /** + * 获取地图ui控制器,可以控制内置ui(缩放按钮、指北针等)是否显示及部分手势(滑动、双指缩放等)是否可用。 + * + * @return + */ + IUiSettings getUiSettings(); + + /** + * 在地图上添一个图片标记(marker)对象。 + * + * @param options + * @return + */ + IMogoMarker addMarker( MogoMarkerOptions options ); + + /** + * 在地图上添一组图片标记(marker)对象,并设置是否改变地图状态以至于所有的marker对象都在当前地图可视区域范围内显示。 + * + * @param options + * @param moveToCenter + * @return + */ + ArrayList< IMogoMarker > addMarkers( ArrayList< MogoMarkerOptions > options, boolean moveToCenter ); + + /** + * 从地图上删除所有的overlay(marker,circle,polyline 等对象)。 + */ + void clear(); + + /** + * 从地图上删除所有的覆盖物(marker,circle,polyline 等对象),但myLocationOverlay(内置定位覆盖物)除外。 + * + * @param isKeepMyLocationOverlay + */ + void clear( boolean isKeepMyLocationOverlay ); + + /** + * 设置屏幕上的某个像素点为地图中心点。 + * + * @param x + * @param y + */ + void setPointToCenter( int x, int y ); + + /** + * 设置地图POI是否允许点击。 + * + * @param touchPoiEnable + */ + void setTouchPoiEnable( boolean touchPoiEnable ); + + /** + * 设置是否打开交通路况图层。 + * + * @param enable + */ + void setTrafficEnable( boolean enable ); + + /** + * 设置是否显示3D建筑物,默认显示。 + * + * @param enabled + */ + void showBuildings( boolean enabled ); + + /** + * 设置是否显示室内地图,默认不显示。 + * + * @param enable + */ + void showIndoorMap( boolean enable ); + + /** + * 设置是否显示底图文字标注,默认显示。 + * + * @param enable + */ + void showMapText( boolean enable ); + + /** + * 停止当前执行的改变地图状态的动画。 + */ + void stopAnimation(); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/IMogoMapView.java b/libraries/mogo-map/src/main/java/com/mogo/map/IMogoMapView.java new file mode 100644 index 0000000000..59c174e296 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/IMogoMapView.java @@ -0,0 +1,21 @@ +package com.mogo.map; + +import android.view.View; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图抽象 + */ +public interface IMogoMapView extends ILifeCycle { + + /** + * 获取地图实例 + * + * @return 地图实例 + */ + View getMapView(); + + IMogoMap getMap(); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/IUiSettings.java b/libraries/mogo-map/src/main/java/com/mogo/map/IUiSettings.java new file mode 100644 index 0000000000..85d1895297 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/IUiSettings.java @@ -0,0 +1,68 @@ +package com.mogo.map; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 图层控制 + */ +public interface IUiSettings { + + /** + * 设置比例尺功能是否可用。 + */ + void setScaleControlsEnabled( boolean enabled ); + + /** + * 这个方法设置了地图是否允许显示缩放按钮。如果允许,则在地图上显示。 + * + * @param enabled + */ + void setZoomControlsEnabled( boolean enabled ); + + /** + * 这个方法设置了地图是否允许显示指南针 + * + * @param enabled + */ + void setCompassEnabled( boolean enabled ); + + /** + * 设置定位按钮是否显示 + * + * @param enabled + */ + void setMyLocationButtonEnabled( boolean enabled ); + + /** + * 这个方法设置了地图是否允许通过手势来移动。如果允许,则用户可以通过按住地图移动来改变可视区域。 + * 如果禁止,则不支持此功能。 这个设置不会影响用户在程序里对地图的移动。 默认移动手势为可用。 + * + * @param enabled + */ + void setScrollGesturesEnabled( boolean enabled ); + + /** + * 这个方法设置了地图是否允许通过手势来缩放。 + * 如果允许,则用户可以通过双击地图或双指在地图上捏合来绽放地图。 + * 这个设置不会影响缩放按钮的功能, 也不会影响程序对地图的操控。 默认允许通过手势缩放地图。 + * + * @param enabled + */ + void setZoomGesturesEnabled( boolean enabled ); + + void setTiltGesturesEnabled( boolean enabled ); + + void setRotateGesturesEnabled( boolean enabled ); + + /** + * 设置当前地图是否支持所有手势。这个设置不影响用户在点击屏幕上的按钮(如缩放按钮)的效果,也不影响用户在程序里对地图的操作。 + * + * @param enabled + */ + void setAllGesturesEnabled( boolean enabled ); + + void setIndoorSwitchEnabled( boolean enabled ); + + void setLogoEnable( boolean enabled ); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/MogoBaseMapView.java b/libraries/mogo-map/src/main/java/com/mogo/map/MogoBaseMapView.java new file mode 100644 index 0000000000..64b4bfee63 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/MogoBaseMapView.java @@ -0,0 +1,114 @@ +package com.mogo.map; + +import android.content.Context; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.Nullable; + +import com.mogo.utils.logger.Logger; + + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 对地图的基本抽象 + */ +public abstract class MogoBaseMapView extends FrameLayout implements ILifeCycle { + + private static final String TAG = "MogoBaseMapView"; + + private IMogoMapView mMapView; + + public MogoBaseMapView( Context context ) { + this( context, null ); + } + + public MogoBaseMapView( Context context, @Nullable AttributeSet attrs ) { + this( context, attrs, 0 ); + } + + public MogoBaseMapView( Context context, @Nullable AttributeSet attrs, int defStyleAttr ) { + super( context, attrs, defStyleAttr ); + init( context ); + } + + private void init( Context context ) { + mMapView = createMapView( context ); + if ( mMapView != null ) { + final View mapView = mMapView.getMapView(); + if ( mapView != null ) { + addView( mapView ); + MogoMap.getInstance().init( context, getMap() ); + } else { + Logger.e( TAG, "create MapView instance failed." ); + } + } else { + Logger.e( TAG, "create IMogoMapView instance failed." ); + } + } + + /** + * 创建地图实例 + * + * @param context + * @return + */ + protected abstract IMogoMapView createMapView( Context context ); + + @Override + public void onCreate( Bundle bundle ) { + if ( mMapView != null ) { + mMapView.onCreate( bundle ); + } + } + + @Override + public void onResume() { + if ( mMapView != null ) { + mMapView.onResume(); + } + } + + @Override + public void onPause() { + if ( mMapView != null ) { + mMapView.onPause(); + } + } + + @Override + public void onDestroy() { + if ( mMapView != null ) { + mMapView.onDestroy(); + } + } + + @Override + public void onSaveInstanceState( Bundle outState ) { + if ( mMapView != null ) { + mMapView.onSaveInstanceState( outState ); + } + } + + @Override + public void onLowMemory() { + if ( mMapView != null ) { + mMapView.onLowMemory(); + } + } + + public IMogoMap getMap() { + if ( mMapView != null ) { + return mMapView.getMap(); + } + return null; + } + + public IMogoMapView getMapView() { + return mMapView; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/MogoLatLng.java b/libraries/mogo-map/src/main/java/com/mogo/map/MogoLatLng.java new file mode 100644 index 0000000000..cfa48c3326 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/MogoLatLng.java @@ -0,0 +1,49 @@ +package com.mogo.map; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 经纬度 + */ +public class MogoLatLng implements Parcelable { + + public final double lat; + public final double lng; + + public MogoLatLng( double lat, double lng ) { + this.lat = lat; + this.lng = lng; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeDouble( this.lat ); + dest.writeDouble( this.lng ); + } + + protected MogoLatLng( Parcel in ) { + this.lat = in.readDouble(); + this.lng = in.readDouble(); + } + + public static final Parcelable.Creator< MogoLatLng > CREATOR = new Parcelable.Creator< MogoLatLng >() { + @Override + public MogoLatLng createFromParcel( Parcel source ) { + return new MogoLatLng( source ); + } + + @Override + public MogoLatLng[] newArray( int size ) { + return new MogoLatLng[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/MogoMap.java b/libraries/mogo-map/src/main/java/com/mogo/map/MogoMap.java new file mode 100644 index 0000000000..7571480782 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/MogoMap.java @@ -0,0 +1,44 @@ +package com.mogo.map; + +import android.content.Context; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * 描述 + */ +public class MogoMap { + + private IMogoMap mMap; + private Context mContext; + + private static volatile MogoMap sInstance; + + private MogoMap() { + } + + public static MogoMap getInstance() { + if ( sInstance == null ) { + synchronized ( MogoMap.class ) { + if ( sInstance == null ) { + sInstance = new MogoMap(); + } + } + } + return sInstance; + } + + public void init( Context context, IMogoMap map ) { + this.mContext = context; + this.mMap = map; + } + + public Context getContext() { + return mContext; + } + + public IMogoMap getMogoMap() { + return mMap; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/MogoMapView.java b/libraries/mogo-map/src/main/java/com/mogo/map/MogoMapView.java new file mode 100644 index 0000000000..342d9d2a83 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/MogoMapView.java @@ -0,0 +1,35 @@ +package com.mogo.map; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +import com.amap.api.navi.AMapNaviView; +import com.mogo.map.amap.AMapNaviViewWrapper; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图实例 + */ +public class MogoMapView extends MogoBaseMapView { + + public MogoMapView( Context context ) { + super( context ); + } + + public MogoMapView( Context context, @Nullable AttributeSet attrs ) { + super( context, attrs ); + } + + public MogoMapView( Context context, @Nullable AttributeSet attrs, int defStyleAttr ) { + super( context, attrs, defStyleAttr ); + } + + @Override + protected IMogoMapView createMapView( Context context ) { + return new AMapNaviViewWrapper( new AMapNaviView( context ) ); + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapNaviViewWrapper.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapNaviViewWrapper.java new file mode 100644 index 0000000000..5dfdcacb11 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapNaviViewWrapper.java @@ -0,0 +1,78 @@ +package com.mogo.map.amap; + +import android.os.Bundle; +import android.view.View; + +import com.amap.api.navi.AMapNaviView; +import com.mogo.map.IMogoMap; +import com.mogo.map.IMogoMapView; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 代理高德导航地图 + */ +public class AMapNaviViewWrapper implements IMogoMapView { + + private final AMapNaviView mMapView; + private IMogoMap mIMap; + + public AMapNaviViewWrapper( AMapNaviView mapView ) { + this.mMapView = mapView; + } + + @Override + public View getMapView() { + return mMapView; + } + + @Override + public IMogoMap getMap() { + if ( mMapView != null ) { + if ( mIMap == null ) { + mIMap = new AMapWrapper( mMapView.getMap() ); + } + } + return mIMap; + } + + @Override + public void onCreate( Bundle bundle ) { + if ( mMapView != null ) { + mMapView.onCreate( bundle ); + } + } + + @Override + public void onResume() { + if ( mMapView != null ) { + mMapView.onResume(); + } + } + + @Override + public void onPause() { + if ( mMapView != null ) { + mMapView.onPause(); + } + } + + @Override + public void onDestroy() { + if ( mMapView != null ) { + mMapView.onDestroy(); + } + } + + @Override + public void onSaveInstanceState( Bundle outState ) { + if ( mMapView != null ) { + mMapView.onSaveInstanceState( outState ); + } + } + + @Override + public void onLowMemory() { + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapUiSettingsWrapper.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapUiSettingsWrapper.java new file mode 100644 index 0000000000..ff966d000e --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapUiSettingsWrapper.java @@ -0,0 +1,104 @@ +package com.mogo.map.amap; + +import com.amap.api.maps.UiSettings; +import com.mogo.map.IUiSettings; + +import java.lang.reflect.Method; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 代理高德地图UiSettings + */ +public class AMapUiSettingsWrapper implements IUiSettings { + + private UiSettings mUiSettings; + + public AMapUiSettingsWrapper( UiSettings mUiSettings ) { + this.mUiSettings = mUiSettings; + } + + @Override + public void setScaleControlsEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setScaleControlsEnabled( enabled ); + } + } + + @Override + public void setZoomControlsEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setZoomControlsEnabled( enabled ); + } + } + + @Override + public void setCompassEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setCompassEnabled( enabled ); + } + } + + @Override + public void setMyLocationButtonEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setMyLocationButtonEnabled( enabled ); + } + } + + @Override + public void setScrollGesturesEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setScrollGesturesEnabled( enabled ); + } + } + + @Override + public void setZoomGesturesEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setZoomGesturesEnabled( enabled ); + } + } + + @Override + public void setTiltGesturesEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setTiltGesturesEnabled( enabled ); + } + } + + @Override + public void setRotateGesturesEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setRotateGesturesEnabled( enabled ); + } + } + + @Override + public void setAllGesturesEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setAllGesturesEnabled( enabled ); + } + } + + @Override + public void setIndoorSwitchEnabled( boolean enabled ) { + if ( mUiSettings != null ) { + mUiSettings.setIndoorSwitchEnabled( enabled ); + } + } + + @Override + public void setLogoEnable( boolean enabled ) { + if ( mUiSettings != null ) { + try { + Method method = mUiSettings.getClass().getMethod( "setLogoEnable", Boolean.class ); + method.setAccessible( true ); + method.invoke( mUiSettings, enabled ); + } catch ( Exception e ) { + e.printStackTrace(); + } + } + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapWrapper.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapWrapper.java new file mode 100644 index 0000000000..834b8b58e4 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/AMapWrapper.java @@ -0,0 +1,153 @@ +package com.mogo.map.amap; + +import com.amap.api.maps.AMap; +import com.amap.api.maps.model.Marker; +import com.amap.api.maps.model.MarkerOptions; +import com.mogo.map.IMogoMap; +import com.mogo.map.marker.IMogoMarker; +import com.mogo.map.IUiSettings; +import com.mogo.map.marker.MogoMarkerOptions; +import com.mogo.map.amap.marker.AMapMarkerWrapper; +import com.mogo.map.amap.utils.ObjectUtils; + +import java.util.ArrayList; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 代理高德AMap + */ +public class AMapWrapper implements IMogoMap { + + private AMap mAMap; + private IUiSettings mUiSettings; + + public AMapWrapper( AMap map ) { + this.mAMap = map; + } + + @Override + public IUiSettings getUiSettings() { + if ( mUiSettings == null ) { + mUiSettings = new AMapUiSettingsWrapper( mAMap.getUiSettings() ); + } + return mUiSettings; + } + + @Override + public IMogoMarker addMarker( MogoMarkerOptions options ) { + if ( mAMap == null ) { + return null; + } + MarkerOptions markerOptions = ObjectUtils.fromMogo( options ); + if ( markerOptions == null ) { + return null; + } + return new AMapMarkerWrapper( mAMap.addMarker( markerOptions ) ); + } + + @Override + public ArrayList< IMogoMarker > addMarkers( ArrayList< MogoMarkerOptions > options, boolean moveToCenter ) { + + if ( mAMap == null ) { + return null; + } + + if ( options == null || options.isEmpty() ) { + return null; + } + + ArrayList< Marker > markers = null; + ArrayList< MarkerOptions > markerOptions = new ArrayList<>(); + ArrayList< IMogoMarker > mogoMarkers = new ArrayList<>(); + + for ( MogoMarkerOptions option : options ) { + if ( option == null ) { + continue; + } + MarkerOptions mo = ObjectUtils.fromMogo( option ); + if ( mo == null ) { + continue; + } + markerOptions.add( mo ); + } + if ( markerOptions.isEmpty() ) { + return null; + } + markers = mAMap.addMarkers( markerOptions, moveToCenter ); + if ( markers == null || markers.isEmpty() ) { + return null; + } + for ( Marker marker : markers ) { + if ( marker == null ) { + continue; + } + mogoMarkers.add( new AMapMarkerWrapper( marker ) ); + } + return mogoMarkers; + } + + @Override + public void clear() { + if ( mAMap != null ) { + mAMap.clear(); + } + } + + @Override + public void clear( boolean isKeepMyLocationOverlay ) { + if ( mAMap != null ) { + mAMap.clear( isKeepMyLocationOverlay ); + } + } + + @Override + public void setPointToCenter( int x, int y ) { + if ( mAMap != null ) { + mAMap.setPointToCenter( x, y ); + } + } + + @Override + public void setTouchPoiEnable( boolean touchPoiEnable ) { + if ( mAMap != null ) { + mAMap.setTouchPoiEnable( touchPoiEnable ); + } + } + + @Override + public void setTrafficEnable( boolean enable ) { + if ( mAMap != null ) { + mAMap.setTrafficEnabled( enable ); + } + } + + @Override + public void showBuildings( boolean enabled ) { + if ( mAMap != null ) { + mAMap.showBuildings( enabled ); + } + } + + @Override + public void showIndoorMap( boolean enable ) { + if ( mAMap != null ) { + mAMap.showIndoorMap( enable ); + } + } + + @Override + public void showMapText( boolean enable ) { + if ( mAMap != null ) { + mAMap.showMapText( enable ); + } + } + + @Override + public void stopAnimation() { + if ( mAMap != null ) { + mAMap.stopAnimation(); + } + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/location/LocationClient.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/location/LocationClient.java new file mode 100644 index 0000000000..d764ec5b82 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/location/LocationClient.java @@ -0,0 +1,116 @@ +package com.mogo.map.amap.location; + +import android.content.Context; + +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.mogo.map.amap.utils.ObjectUtils; +import com.mogo.map.location.ILocationListener; +import com.mogo.map.location.IMogoLocationClient; +import com.mogo.map.location.MogoLocation; +import com.mogo.utils.logger.Logger; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 高德定位 + */ +public class LocationClient implements IMogoLocationClient { + + private static final String TAG = "LocationClient"; + + private static volatile LocationClient sInstance; + private static Set< ILocationListener > sListeners = new HashSet<>( 10 ); + private static MogoLocation sLastLocation = new MogoLocation(); + + private LocationClient( Context context ) { + mClient = new AMapLocationClient( context ); + mClient.setLocationListener( new InternalLocationListener() ); + } + + public static LocationClient getInstance( Context context ) { + if ( sInstance == null ) { + synchronized ( LocationClient.class ) { + if ( sInstance == null ) { + sInstance = new LocationClient( context ); + } + } + } + return sInstance; + } + + private AMapLocationClient mClient; + + @Override + public void start() { + start( 2_000L ); + } + + @Override + public void start( long interval ) { + stop(); + AMapLocationClientOption option = new AMapLocationClientOption(); + option.setLocationMode( AMapLocationClientOption.AMapLocationMode.Hight_Accuracy ); + option.setNeedAddress( true ); + option.setInterval( interval ); + if ( mClient != null ) { + mClient.setLocationOption( option ); + } + } + + @Override + public void stop() { + if ( mClient != null ) { + mClient.stopLocation(); + } + } + + @Override + public void addLocationListener( ILocationListener listener ) { + if ( listener != null ) { + synchronized ( sListeners ) { + sListeners.add( listener ); + } + } + } + + @Override + public void removeLocationListener( ILocationListener listener ) { + if ( listener != null ) { + synchronized ( sListeners ) { + sListeners.remove( listener ); + } + } + } + + @Override + public MogoLocation getLastKnowLocation() { + return sLastLocation; + } + + private static class InternalLocationListener implements AMapLocationListener { + @Override + public void onLocationChanged( AMapLocation aMapLocation ) { + if ( aMapLocation == null || + aMapLocation.getLatitude() == 0.0D || + aMapLocation.getLongitude() == 0.0D ) { + return; + } + Logger.d( TAG, aMapLocation.toString() ); + sLastLocation = ObjectUtils.fromAMap( aMapLocation ); + synchronized ( sListeners ) { + Iterator< ILocationListener > listenerIterator = sListeners.iterator(); + while ( listenerIterator.hasNext() ) { + listenerIterator.next().onLocationChanged( sLastLocation.clone() ); + } + } + } + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/marker/AMapMarkerWrapper.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/marker/AMapMarkerWrapper.java new file mode 100644 index 0000000000..b89701ae59 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/marker/AMapMarkerWrapper.java @@ -0,0 +1,190 @@ +package com.mogo.map.amap.marker; + +import android.graphics.Bitmap; + +import com.amap.api.maps.model.BitmapDescriptor; +import com.amap.api.maps.model.BitmapDescriptorFactory; +import com.amap.api.maps.model.LatLng; +import com.amap.api.maps.model.Marker; +import com.amap.api.maps.model.MarkerOptions; +import com.mogo.map.marker.IMogoMarker; +import com.mogo.map.marker.MogoMarkerOptions; +import com.mogo.map.amap.utils.ObjectUtils; + +import java.util.ArrayList; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 代理高德marker + */ +public class AMapMarkerWrapper implements IMogoMarker { + + private Marker mMarker; + + public AMapMarkerWrapper( Marker mMarker ) { + this.mMarker = mMarker; + } + + @Override + public void destroy() { + if ( mMarker != null ) { + mMarker.destroy(); + } + } + + @Override + public void remove() { + if ( mMarker != null ) { + mMarker.remove(); + } + } + + @Override + public void hideInfoWindow() { + if ( mMarker != null ) { + mMarker.hideInfoWindow(); + } + } + + @Override + public void setAlpha( float alpha ) { + if ( mMarker != null ) { + mMarker.setAlpha( alpha ); + } + } + + @Override + public void setAnchor( float anchorU, float anchorV ) { + if ( mMarker != null ) { + mMarker.setAnchor( anchorU, anchorV ); + } + } + + @Override + public void setDraggable( boolean paramBoolean ) { + if ( mMarker != null ) { + mMarker.setDraggable( paramBoolean ); + } + } + + @Override + public void setIcon( Bitmap icon ) { + if ( icon == null || icon.isRecycled() ) { + return; + } + if ( mMarker != null ) { + mMarker.setIcon( BitmapDescriptorFactory.fromBitmap( icon ) ); + } + } + + @Override + public void setIcons( ArrayList< Bitmap > icons ) { + if ( icons == null || icons.isEmpty() ) { + return; + } + ArrayList< BitmapDescriptor > descriptors = new ArrayList<>(); + for ( Bitmap icon : icons ) { + if ( icon == null || icon.isRecycled() ) { + continue; + } + descriptors.add( BitmapDescriptorFactory.fromBitmap( icon ) ); + } + if ( descriptors.isEmpty() ) { + return; + } + if ( mMarker != null ) { + mMarker.setIcons( descriptors ); + } + } + + @Override + public void setInfoWindowEnable( boolean enabled ) { + if ( mMarker != null ) { + mMarker.setInfoWindowEnable( enabled ); + } + } + + @Override + public void setMarkerOptions( MogoMarkerOptions opt ) { + + final MarkerOptions options = ObjectUtils.fromMogo( opt ); + if ( options == null ) { + return; + } + if ( mMarker != null ) { + mMarker.setMarkerOptions( options ); + } + } + + @Override + public void setObject( Object object ) { + if ( mMarker != null ) { + mMarker.setObject( object ); + } + } + + @Override + public void setPeriod( int period ) { + if ( mMarker != null ) { + mMarker.setPeriod( period ); + } + } + + @Override + public void setPosition( double lat, double lng ) { + if ( mMarker != null ) { + mMarker.setPosition( new LatLng( lat, lng ) ); + } + } + + @Override + public void setRotateAngle( float rotate ) { + if ( mMarker != null ) { + mMarker.setRotateAngle( rotate ); + } + } + + @Override + public void setSnippet( String snippet ) { + if ( mMarker != null ) { + mMarker.setSnippet( snippet ); + } + } + + @Override + public void setTitle( String title ) { + if ( mMarker != null ) { + mMarker.setTitle( title ); + } + } + + @Override + public void setToTop() { + if ( mMarker != null ) { + mMarker.setToTop(); + } + } + + @Override + public void setVisible( boolean visible ) { + if ( mMarker != null ) { + mMarker.setVisible( visible ); + } + } + + @Override + public void setZIndex( float zIndex ) { + if ( mMarker != null ) { + mMarker.setZIndex( zIndex ); + } + } + + @Override + public void showInfoWindow() { + if ( mMarker != null ) { + mMarker.showInfoWindow(); + } + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/search/GeocodeSearchClient.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/search/GeocodeSearchClient.java new file mode 100644 index 0000000000..974ae5563d --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/search/GeocodeSearchClient.java @@ -0,0 +1,101 @@ +package com.mogo.map.amap.search; + +import android.content.Context; + +import com.amap.api.services.core.AMapException; +import com.amap.api.services.geocoder.GeocodeAddress; +import com.amap.api.services.geocoder.GeocodeResult; +import com.amap.api.services.geocoder.GeocodeSearch; +import com.amap.api.services.geocoder.RegeocodeAddress; +import com.amap.api.services.geocoder.RegeocodeResult; +import com.mogo.map.amap.utils.ObjectUtils; +import com.mogo.map.exception.MogoMapException; +import com.mogo.map.search.IMogoGeoSearch; +import com.mogo.map.search.IMogoGeoSearchListener; +import com.mogo.map.search.MogoGeocodeAddress; +import com.mogo.map.search.MogoRegeocodeAddress; +import com.mogo.map.search.query.MogoGeocodeQuery; +import com.mogo.map.search.query.MogoRegeocodeQuery; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 地理编码/逆地理编码高德实现 + */ +public class GeocodeSearchClient implements IMogoGeoSearch, GeocodeSearch.OnGeocodeSearchListener { + + private GeocodeSearch mClient; + private IMogoGeoSearchListener mListener; + + public GeocodeSearchClient( Context context ) { + mClient = new GeocodeSearch( context ); + mClient.setOnGeocodeSearchListener( this ); + } + + @Override + public void setGeoSearchListener( IMogoGeoSearchListener listener ) { + mListener = listener; + } + + @Override + public MogoRegeocodeAddress getFromLocation( MogoRegeocodeQuery query ) throws MogoMapException { + try { + RegeocodeAddress regeocodeAddress = mClient.getFromLocation( ObjectUtils.fromMogo( query ) ); + return ObjectUtils.fromAMap( regeocodeAddress ); + } catch ( AMapException e ) { + throw new MogoMapException( e ); + } + } + + @Override + public List< MogoGeocodeAddress > getFromLocationName( MogoGeocodeQuery query ) throws MogoMapException { + try { + List< GeocodeAddress > geocodeAddress = mClient.getFromLocationName( ObjectUtils.fromMogo( query ) ); + if ( geocodeAddress != null ) { + List< MogoGeocodeAddress > mogoGeocodeAddresses = new ArrayList<>(); + for ( GeocodeAddress address : geocodeAddress ) { + MogoGeocodeAddress mogoGeocodeAddress = ObjectUtils.fromAMap( address ); + if ( mogoGeocodeAddress != null ) { + mogoGeocodeAddresses.add( mogoGeocodeAddress ); + } + } + return mogoGeocodeAddresses; + } + return new ArrayList<>(); + } catch ( AMapException e ) { + throw new MogoMapException( e ); + } + } + + @Override + public void getFromLocationAsyn( MogoRegeocodeQuery query ) { + if ( mClient != null ) { + mClient.getFromLocationAsyn( ObjectUtils.fromMogo( query ) ); + } + } + + @Override + public void getFromLocationNameAsyn( MogoGeocodeQuery query ) { + if ( mClient != null ) { + mClient.getFromLocationNameAsyn( ObjectUtils.fromMogo( query ) ); + } + } + + @Override + public void onRegeocodeSearched( RegeocodeResult regeocodeResult, int i ) { + if ( mListener != null ) { + mListener.onRegeocodeSearched( ObjectUtils.fromAMap( regeocodeResult ), i ); + } + } + + @Override + public void onGeocodeSearched( GeocodeResult geocodeResult, int i ) { + if ( mListener != null ) { + mListener.onGeocodeSearched( ObjectUtils.fromAMap( geocodeResult ), i ); + } + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/search/InputtipsSearch.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/search/InputtipsSearch.java new file mode 100644 index 0000000000..9be69939df --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/search/InputtipsSearch.java @@ -0,0 +1,84 @@ +package com.mogo.map.amap.search; + +import android.content.Context; + +import com.amap.api.services.core.AMapException; +import com.amap.api.services.help.Inputtips; +import com.amap.api.services.help.InputtipsQuery; +import com.amap.api.services.help.Tip; +import com.mogo.map.amap.utils.ObjectUtils; +import com.mogo.map.exception.MogoMapException; +import com.mogo.map.search.IMogoInputtipsListener; +import com.mogo.map.search.IMogoInputtipsSearch; +import com.mogo.map.search.MogoTip; +import com.mogo.map.search.query.MogoInputtipsQuery; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * 高德地图 inputtips搜索实现 + */ +public class InputtipsSearch implements IMogoInputtipsSearch, Inputtips.InputtipsListener { + + private Inputtips mClient; + private InputtipsQuery mQuery; + private IMogoInputtipsListener mListener; + + public InputtipsSearch( Context context, MogoInputtipsQuery query ) { + mQuery = ObjectUtils.fromMogo( query ); + mClient = new Inputtips( context, mQuery ); + mClient.setInputtipsListener( this ); + } + + @Override + public void setQuery( MogoInputtipsQuery query ) { + this.mQuery = ObjectUtils.fromMogo( query ); + } + + @Override + public void setInputtipsListener( IMogoInputtipsListener listener ) { + this.mListener = listener; + } + + @Override + public void requestInputtipsAsyn() { + if ( mClient != null ) { + mClient.requestInputtipsAsyn(); + } + } + + @Override + public List< MogoTip > requestInputtips() throws MogoMapException { + try { + List< Tip > tips = mClient.requestInputtips(); + List< MogoTip > mogoTips = getResult( tips ); + return mogoTips; + } catch ( AMapException e ) { + throw new MogoMapException( e ); + } + } + + @Override + public void onGetInputtips( List< Tip > list, int i ) { + if ( mListener != null ) { + mListener.onGetInputtips( getResult( list ), i ); + } + } + + private List< MogoTip > getResult( List< Tip > tips ) { + List< MogoTip > mogoTips = new ArrayList<>(); + if ( tips != null ) { + for ( Tip tip : tips ) { + MogoTip mogoTip = ObjectUtils.fromAMap( tip ); + if ( mogoTip != null ) { + mogoTips.add( mogoTip ); + } + } + } + return mogoTips; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/amap/utils/ObjectUtils.java b/libraries/mogo-map/src/main/java/com/mogo/map/amap/utils/ObjectUtils.java new file mode 100644 index 0000000000..76222d54f2 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/amap/utils/ObjectUtils.java @@ -0,0 +1,466 @@ +package com.mogo.map.amap.utils; + +import android.graphics.Bitmap; + +import com.amap.api.location.AMapLocation; +import com.amap.api.maps.model.BitmapDescriptor; +import com.amap.api.maps.model.BitmapDescriptorFactory; +import com.amap.api.maps.model.LatLng; +import com.amap.api.maps.model.MarkerOptions; +import com.amap.api.services.core.LatLonPoint; +import com.amap.api.services.core.PoiItem; +import com.amap.api.services.geocoder.AoiItem; +import com.amap.api.services.geocoder.BusinessArea; +import com.amap.api.services.geocoder.GeocodeAddress; +import com.amap.api.services.geocoder.GeocodeQuery; +import com.amap.api.services.geocoder.GeocodeResult; +import com.amap.api.services.geocoder.RegeocodeAddress; +import com.amap.api.services.geocoder.RegeocodeQuery; +import com.amap.api.services.geocoder.RegeocodeResult; +import com.amap.api.services.geocoder.RegeocodeRoad; +import com.amap.api.services.geocoder.StreetNumber; +import com.amap.api.services.help.InputtipsQuery; +import com.amap.api.services.help.Tip; +import com.amap.api.services.poisearch.IndoorData; +import com.amap.api.services.poisearch.Photo; +import com.amap.api.services.poisearch.PoiItemExtension; +import com.amap.api.services.poisearch.SubPoiItem; +import com.amap.api.services.road.Crossroad; +import com.mogo.map.MogoLatLng; +import com.mogo.map.location.MogoLocation; +import com.mogo.map.marker.MogoMarkerOptions; +import com.mogo.map.search.MogoAoiItem; +import com.mogo.map.search.MogoBusinessArea; +import com.mogo.map.search.MogoCrossroad; +import com.mogo.map.search.MogoGeocodeAddress; +import com.mogo.map.search.MogoGeocodeResult; +import com.mogo.map.search.MogoIndoorData; +import com.mogo.map.search.MogoPhoto; +import com.mogo.map.search.MogoPoiItem; +import com.mogo.map.search.MogoPoiItemExtension; +import com.mogo.map.search.MogoRegeocodeAddress; +import com.mogo.map.search.MogoRegeocodeResult; +import com.mogo.map.search.MogoRegeocodeRoad; +import com.mogo.map.search.MogoStreetNumber; +import com.mogo.map.search.MogoSubPoiItem; +import com.mogo.map.search.MogoTip; +import com.mogo.map.search.query.MogoGeocodeQuery; +import com.mogo.map.search.query.MogoInputtipsQuery; +import com.mogo.map.search.query.MogoRegeocodeQuery; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 业务对象和实际对象转换 + */ +public class ObjectUtils { + + public static MarkerOptions fromMogo( MogoMarkerOptions opt ) { + + if ( opt == null ) { + return null; + } + ArrayList< BitmapDescriptor > descriptors = new ArrayList<>(); + final ArrayList< Bitmap > icons = opt.getIcons(); + if ( icons != null && !icons.isEmpty() ) { + for ( Bitmap icon : icons ) { + if ( icon == null || icon.isRecycled() ) { + continue; + } + descriptors.add( BitmapDescriptorFactory.fromBitmap( icon ) ); + } + } + + return new MarkerOptions() + .position( new LatLng( opt.getLatitude(), opt.getLongitude() ) ) + .title( opt.getTitle() ) + .snippet( opt.getSnippet() ) + .icon( BitmapDescriptorFactory.fromBitmap( opt.getIcon() ) ) + .icons( descriptors ) + .period( opt.getPeriod() ) + .rotateAngle( opt.getRotate() ) + .setFlat( opt.isFlat() ) + .visible( opt.isVisible() ) + .infoWindowEnable( opt.isInifoWindowEnable() ) + .alpha( opt.getAlpha() ) + .setGps( opt.isGps() ) + .anchor( opt.getU(), opt.getV() ) + .draggable( opt.isDraggable() ) + .setInfoWindowOffset( opt.getOffsetX(), opt.getOffsetY() ) + .zIndex( opt.getzIndex() ); + } + + + public static MogoLocation fromAMap( AMapLocation aLocation ) { + if ( aLocation == null ) { + return null; + } + MogoLocation location = new MogoLocation(); + location.setLocType( aLocation.getLocationType() ); + location.setSpeed( aLocation.getSpeed() ); + location.setLatitude( aLocation.getLatitude() ); + location.setLongitude( aLocation.getLongitude() ); + location.setAltitude( aLocation.getAltitude() ); + location.setTime( aLocation.getTime() ); + location.setBearing( aLocation.getBearing() ); + location.setAccuracy( aLocation.getAccuracy() ); + location.setCityCode( aLocation.getCityCode() ); + location.setCityName( aLocation.getCity() ); + location.setProvider( aLocation.getProvider() ); + location.setAddress( aLocation.getAddress() ); + location.setDistrict( aLocation.getDistrict() ); + location.setProvince( aLocation.getProvince() ); + location.setAdCode( aLocation.getAdCode() ); + location.setLocationDetail( aLocation.getLocationDetail() ); + location.setPoiName( aLocation.getPoiName() ); + location.setAoiName( aLocation.getAoiName() ); + location.setErrCode( aLocation.getErrorCode() ); + location.setErrInfo( aLocation.getErrorInfo() ); + location.setStreetNum( aLocation.getStreetNum() ); + location.setDescription( aLocation.getDescription() ); + location.setBuildingId( aLocation.getBuildingId() ); + location.setFloor( aLocation.getFloor() ); + location.setGpsAccuracyStatus( aLocation.getGpsAccuracyStatus() ); + location.setSatellite( aLocation.getSatellites() ); + return location; + } + + public static LatLonPoint fromMogo( MogoLatLng latLng ) { + if ( latLng == null ) { + return null; + } + return new LatLonPoint( latLng.lat, latLng.lng ); + } + + public static MogoLatLng fromAMap( LatLonPoint point ) { + if ( point == null ) { + return null; + } + return new MogoLatLng( point.getLatitude(), point.getLongitude() ); + } + + public static GeocodeQuery fromMogo( MogoGeocodeQuery query ) { + if ( query == null ) { + return null; + } + GeocodeQuery q = new GeocodeQuery( query.getLocationName(), query.getCity() ); + return q; + } + + public static RegeocodeQuery fromMogo( MogoRegeocodeQuery query ) { + if ( query == null ) { + return null; + } + RegeocodeQuery q = new RegeocodeQuery( fromMogo( query.getPoint() ), query.getRadius(), query.getLatlngType() ); + return q; + } + + public static MogoAoiItem fromAMap( AoiItem amapItem ) { + if ( amapItem == null ) { + return null; + } + MogoAoiItem mogoAoiItem = new MogoAoiItem(); + mogoAoiItem.setAdCode( amapItem.getAdCode() ); + mogoAoiItem.setAoiArea( amapItem.getAoiArea() ); + mogoAoiItem.setAoiCenterPoint( fromAMap( amapItem.getAoiCenterPoint() ) ); + mogoAoiItem.setAoiId( amapItem.getAoiId() ); + mogoAoiItem.setAoiName( amapItem.getAoiName() ); + return mogoAoiItem; + } + + public static MogoBusinessArea fromAMap( BusinessArea amapItem ) { + if ( amapItem == null ) { + return null; + } + MogoBusinessArea mogoBusinessArea = new MogoBusinessArea(); + mogoBusinessArea.setCenterPoint( fromAMap( amapItem.getCenterPoint() ) ); + mogoBusinessArea.setName( amapItem.getName() ); + return mogoBusinessArea; + } + + + public static MogoCrossroad fromAMap( Crossroad amapItem ) { + if ( amapItem == null ) { + return null; + } + MogoCrossroad mogoCrossroad = new MogoCrossroad(); + mogoCrossroad.setDirection( amapItem.getDirection() ); + mogoCrossroad.setDistance( amapItem.getDistance() ); + mogoCrossroad.setFirstRoadId( amapItem.getFirstRoadId() ); + mogoCrossroad.setFirstRoadName( amapItem.getFirstRoadName() ); + mogoCrossroad.setSecondRoadId( amapItem.getSecondRoadId() ); + mogoCrossroad.setSecondRoadName( amapItem.getSecondRoadName() ); + return mogoCrossroad; + } + + public static MogoGeocodeAddress fromAMap( GeocodeAddress address ) { + if ( address == null ) { + return null; + } + MogoGeocodeAddress mogoGeocodeAddress = new MogoGeocodeAddress(); + mogoGeocodeAddress.setAdcode( address.getAdcode() ); + mogoGeocodeAddress.setBuilding( address.getBuilding() ); + mogoGeocodeAddress.setCity( address.getCity() ); + mogoGeocodeAddress.setDistrict( address.getDistrict() ); + mogoGeocodeAddress.setFormatAddress( address.getFormatAddress() ); + mogoGeocodeAddress.setLatlng( fromAMap( address.getLatLonPoint() ) ); + mogoGeocodeAddress.setLevel( address.getLevel() ); + mogoGeocodeAddress.setNeighborhood( address.getNeighborhood() ); + mogoGeocodeAddress.setProvince( address.getProvince() ); + mogoGeocodeAddress.setTownship( address.getTownship() ); + return mogoGeocodeAddress; + } + + public static MogoGeocodeResult fromAMap( GeocodeResult result ) { + if ( result == null || result.getGeocodeAddressList() != null ) { + return null; + } + MogoGeocodeResult mogoGeocodeResult = new MogoGeocodeResult(); + final List< MogoGeocodeAddress > addresses = new ArrayList<>(); + for ( GeocodeAddress geocodeAddress : result.getGeocodeAddressList() ) { + final MogoGeocodeAddress mogoGeocodeAddress = fromAMap( geocodeAddress ); + if ( mogoGeocodeAddress != null ) { + addresses.add( mogoGeocodeAddress ); + } + } + + mogoGeocodeResult.setAddresses( addresses ); + return mogoGeocodeResult; + } + + public static MogoIndoorData fromAMap( IndoorData data ) { + if ( data == null ) { + return null; + } + MogoIndoorData mogoIndoorData = new MogoIndoorData(); + mogoIndoorData.setFloor( data.getFloor() ); + mogoIndoorData.setFloorName( data.getFloorName() ); + mogoIndoorData.setPoiId( data.getPoiId() ); + return mogoIndoorData; + } + + public static MogoPhoto fromAMap( Photo photo ) { + if ( photo == null ) { + return null; + } + MogoPhoto mogoPhoto = new MogoPhoto(); + mogoPhoto.setTitle( photo.getTitle() ); + mogoPhoto.setUrl( photo.getUrl() ); + return mogoPhoto; + } + + public static MogoPoiItemExtension fromAMap( PoiItemExtension poiItemExtension ) { + if ( poiItemExtension == null ) { + return null; + } + MogoPoiItemExtension mogoPoiItemExtension = new MogoPoiItemExtension(); + mogoPoiItemExtension.setOpentime( poiItemExtension.getOpentime() ); + mogoPoiItemExtension.setRating( poiItemExtension.getmRating() ); + return mogoPoiItemExtension; + } + + public static MogoRegeocodeRoad fromAMap( RegeocodeRoad regeocodeRoad ) { + if ( regeocodeRoad == null ) { + return null; + } + MogoRegeocodeRoad mogoRegeocodeRoad = new MogoRegeocodeRoad(); + mogoRegeocodeRoad.setDirection( regeocodeRoad.getDirection() ); + mogoRegeocodeRoad.setDistance( regeocodeRoad.getDistance() ); + mogoRegeocodeRoad.setId( regeocodeRoad.getId() ); + mogoRegeocodeRoad.setName( regeocodeRoad.getName() ); + mogoRegeocodeRoad.setPoint( fromAMap( regeocodeRoad.getLatLngPoint() ) ); + return mogoRegeocodeRoad; + } + + public static MogoStreetNumber fromAMap( StreetNumber streetNumber ) { + if ( streetNumber == null ) { + return null; + } + MogoStreetNumber mogoStreetNumber = new MogoStreetNumber(); + mogoStreetNumber.setDirection( streetNumber.getDirection() ); + mogoStreetNumber.setDistance( streetNumber.getDistance() ); + mogoStreetNumber.setLatLonPoint( fromAMap( streetNumber.getLatLonPoint() ) ); + mogoStreetNumber.setNumber( streetNumber.getNumber() ); + mogoStreetNumber.setStreet( streetNumber.getStreet() ); + return mogoStreetNumber; + } + + public static MogoSubPoiItem fromAMap( SubPoiItem subPoiItem ) { + if ( subPoiItem == null ) { + return null; + } + MogoSubPoiItem mogoSubPoiItem = new MogoSubPoiItem(); + mogoSubPoiItem.setDistance( subPoiItem.getDistance() ); + mogoSubPoiItem.setPoiId( subPoiItem.getPoiId() ); + mogoSubPoiItem.setPoint( fromAMap( subPoiItem.getLatLonPoint() ) ); + mogoSubPoiItem.setSnippet( subPoiItem.getSnippet() ); + mogoSubPoiItem.setSubName( mogoSubPoiItem.getSubName() ); + mogoSubPoiItem.setSubTypeDes( mogoSubPoiItem.getSubTypeDes() ); + mogoSubPoiItem.setTitle( mogoSubPoiItem.getTitle() ); + return mogoSubPoiItem; + } + + public static MogoPoiItem fromAMap( PoiItem poiItem ) { + if ( poiItem == null ) { + return null; + } + MogoPoiItem mogoPoiItem = new MogoPoiItem(); + mogoPoiItem.setAdCode( poiItem.getAdCode() ); + mogoPoiItem.setAdName( poiItem.getAdName() ); + mogoPoiItem.setBusinessArea( poiItem.getBusinessArea() ); + mogoPoiItem.setCityCode( poiItem.getCityCode() ); + mogoPoiItem.setCityName( poiItem.getCityName() ); + mogoPoiItem.setDirection( poiItem.getDirection() ); + mogoPoiItem.setDistance( poiItem.getDistance() ); + mogoPoiItem.setEmail( poiItem.getEmail() ); + mogoPoiItem.setEnter( fromAMap( poiItem.getEnter() ) ); + mogoPoiItem.setExit( fromAMap( poiItem.getExit() ) ); + mogoPoiItem.setIndoorData( fromAMap( poiItem.getIndoorData() ) ); + mogoPoiItem.setParkingType( poiItem.getParkingType() ); + mogoPoiItem.setIndoorMap( poiItem.isIndoorMap() ); + if ( poiItem.getPhotos() != null ) { + List< MogoPhoto > mogoPhotos = new ArrayList<>(); + for ( Photo photo : poiItem.getPhotos() ) { + MogoPhoto mogoPhoto = fromAMap( photo ); + if ( mogoPhoto != null ) { + mogoPhotos.add( mogoPhoto ); + } + } + mogoPoiItem.setPhotos( mogoPhotos ); + } + mogoPoiItem.setPoiExtension( fromAMap( poiItem.getPoiExtension() ) ); + mogoPoiItem.setPoiId( poiItem.getPoiId() ); + mogoPoiItem.setPoint( fromAMap( poiItem.getLatLonPoint() ) ); + mogoPoiItem.setPostcode( poiItem.getPostcode() ); + mogoPoiItem.setProvinceCode( poiItem.getProvinceCode() ); + mogoPoiItem.setProvinceName( poiItem.getProvinceName() ); + mogoPoiItem.setShopID( poiItem.getShopID() ); + mogoPoiItem.setSnippet( poiItem.getSnippet() ); + if ( poiItem.getSubPois() != null ) { + List< MogoSubPoiItem > mogoSubPoiItems = new ArrayList<>(); + for ( SubPoiItem subPois : poiItem.getSubPois() ) { + MogoSubPoiItem mogoSubPoiItem = fromAMap( subPois ); + if ( mogoPoiItem != null ) { + mogoSubPoiItems.add( mogoSubPoiItem ); + } + } + mogoPoiItem.setSubPois( mogoSubPoiItems ); + } + mogoPoiItem.setTel( poiItem.getTel() ); + mogoPoiItem.setTypeCode( poiItem.getTypeCode() ); + mogoPoiItem.setTitle( poiItem.getTitle() ); + mogoPoiItem.setTypeDes( poiItem.getTypeDes() ); + mogoPoiItem.setWebsite( poiItem.getWebsite() ); + return mogoPoiItem; + } + + public static MogoRegeocodeAddress fromAMap( RegeocodeAddress regeocodeAddress ) { + if ( regeocodeAddress == null ) { + return null; + } + MogoRegeocodeAddress mogoRegeocodeAddress = new MogoRegeocodeAddress(); + mogoRegeocodeAddress.setAdCode( regeocodeAddress.getAdCode() ); + if ( regeocodeAddress.getAois() != null ) { + List< MogoAoiItem > items = new ArrayList<>(); + for ( AoiItem aois : regeocodeAddress.getAois() ) { + final MogoAoiItem mogoAoiItem = fromAMap( aois ); + if ( mogoAoiItem != null ) { + items.add( mogoAoiItem ); + } + } + mogoRegeocodeAddress.setAois( items ); + } + + mogoRegeocodeAddress.setBuilding( regeocodeAddress.getBuilding() ); + if ( regeocodeAddress.getBusinessAreas() != null ) { + List< MogoBusinessArea > mogoBusinessAreas = new ArrayList<>(); + for ( BusinessArea businessArea : regeocodeAddress.getBusinessAreas() ) { + MogoBusinessArea mogoBusinessArea = fromAMap( businessArea ); + if ( mogoBusinessArea != null ) { + mogoBusinessAreas.add( mogoBusinessArea ); + } + } + mogoRegeocodeAddress.setBusinessAreas( mogoBusinessAreas ); + } + + mogoRegeocodeAddress.setCity( regeocodeAddress.getCity() ); + mogoRegeocodeAddress.setCityCode( regeocodeAddress.getCityCode() ); + mogoRegeocodeAddress.setCountry( regeocodeAddress.getCountry() ); + if ( regeocodeAddress.getCrossroads() != null ) { + List< MogoCrossroad > mogoCrossroads = new ArrayList<>(); + for ( Crossroad crossroad : regeocodeAddress.getCrossroads() ) { + + MogoCrossroad mogoCrossroad = fromAMap( crossroad ); + if ( mogoCrossroad != null ) { + mogoCrossroads.add( mogoCrossroad ); + } + } + mogoRegeocodeAddress.setCrossroads( mogoCrossroads ); + } + mogoRegeocodeAddress.setDistrict( regeocodeAddress.getDistrict() ); + mogoRegeocodeAddress.setFormatAddress( regeocodeAddress.getFormatAddress() ); + mogoRegeocodeAddress.setNeighborhood( regeocodeAddress.getNeighborhood() ); + if ( regeocodeAddress.getPois() != null ) { + List< MogoPoiItem > mogoPoiItems = new ArrayList<>(); + for ( PoiItem pois : regeocodeAddress.getPois() ) { + MogoPoiItem mogoPoiItem = fromAMap( pois ); + mogoPoiItems.add( mogoPoiItem ); + } + mogoRegeocodeAddress.setPois( mogoPoiItems ); + } + mogoRegeocodeAddress.setProvince( regeocodeAddress.getProvince() ); + if ( regeocodeAddress.getRoads() != null ) { + List< MogoRegeocodeRoad > mogoRegeocodeRoads = new ArrayList<>(); + for ( RegeocodeRoad road : regeocodeAddress.getRoads() ) { + MogoRegeocodeRoad mogoRegeocodeRoad = fromAMap( road ); + if ( mogoRegeocodeRoad != null ) { + mogoRegeocodeRoads.add( mogoRegeocodeRoad ); + } + } + mogoRegeocodeAddress.setRoads( mogoRegeocodeRoads ); + } + mogoRegeocodeAddress.setStreetNumber( fromAMap( regeocodeAddress.getStreetNumber() ) ); + mogoRegeocodeAddress.setTowncode( regeocodeAddress.getTowncode() ); + mogoRegeocodeAddress.setTownship( regeocodeAddress.getTownship() ); + return mogoRegeocodeAddress; + } + + public static MogoRegeocodeResult fromAMap( RegeocodeResult regeocodeResult ) { + if ( regeocodeResult == null ) { + return null; + } + MogoRegeocodeResult mogoRegeocodeResult = new MogoRegeocodeResult(); + mogoRegeocodeResult.setRegeocodeAddress( fromAMap( regeocodeResult.getRegeocodeAddress() ) ); + return mogoRegeocodeResult; + } + + public static InputtipsQuery fromMogo( MogoInputtipsQuery query ) { + if ( query == null ) { + return null; + } + InputtipsQuery inputtipsQuery = new InputtipsQuery( query.getKeyword(), query.getCity() ); + inputtipsQuery.setCityLimit( query.isCityLimit() ); + inputtipsQuery.setLocation( fromMogo( query.getLocation() ) ); + inputtipsQuery.setType( query.getType() ); + return inputtipsQuery; + } + + public static MogoTip fromAMap( Tip tip ) { + if ( tip == null ) { + return null; + } + MogoTip mogoTip = new MogoTip(); + mogoTip.setAdCode( tip.getAdcode() ); + mogoTip.setAddress( tip.getAddress() ); + mogoTip.setDistrict( tip.getDistrict() ); + mogoTip.setName( tip.getName() ); + mogoTip.setPoiID( tip.getPoiID() ); + mogoTip.setPoint( fromAMap( tip.getPoint() ) ); + mogoTip.setTypeCode( tip.getTypeCode() ); + return mogoTip; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/exception/MogoMapException.java b/libraries/mogo-map/src/main/java/com/mogo/map/exception/MogoMapException.java new file mode 100644 index 0000000000..1c85abbecc --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/exception/MogoMapException.java @@ -0,0 +1,25 @@ +package com.mogo.map.exception; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 异常 + */ +public class MogoMapException extends Exception { + + public MogoMapException() { + } + + public MogoMapException( String message ) { + super( message ); + } + + public MogoMapException( String message, Throwable cause ) { + super( message, cause ); + } + + public MogoMapException( Throwable cause ) { + super( cause ); + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/location/ILocationListener.java b/libraries/mogo-map/src/main/java/com/mogo/map/location/ILocationListener.java new file mode 100644 index 0000000000..7069166e20 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/location/ILocationListener.java @@ -0,0 +1,17 @@ +package com.mogo.map.location; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 定位回调 + */ +public interface ILocationListener { + + /** + * 定位发生改变 + * + * @param location 新定位点 + */ + void onLocationChanged( MogoLocation location ); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/location/IMogoLocationClient.java b/libraries/mogo-map/src/main/java/com/mogo/map/location/IMogoLocationClient.java new file mode 100644 index 0000000000..ea24dac392 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/location/IMogoLocationClient.java @@ -0,0 +1,43 @@ +package com.mogo.map.location; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 定位接口 + */ +public interface IMogoLocationClient { + + /** + * 开始定位 + */ + void start(); + + /** + * 开始定位 + * + * @param interval 默认定位间隔 + */ + void start( long interval ); + + /** + * 停止定位 + */ + void stop(); + + /** + * 注册定位回调 + * + * @param listener + */ + void addLocationListener( ILocationListener listener ); + + /** + * 注销定位回调 + * + * @param listener + */ + void removeLocationListener( ILocationListener listener ); + + MogoLocation getLastKnowLocation(); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocation.java b/libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocation.java new file mode 100644 index 0000000000..f41f924c0b --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocation.java @@ -0,0 +1,362 @@ +package com.mogo.map.location; + +import android.os.Parcel; +import android.os.Parcelable; + +public class MogoLocation implements Cloneable, Parcelable { + private int locType = 0; + private double latitude = 0; + private double longitude = 0; + private double altitude = 0; + private long time = 0; + private float bearing = 0; + private float accuracy = 0; + private float speed = 0; + private String cityName = ""; + private String cityCode = ""; + private String provider = ""; + private String address = ""; + private String district = ""; + private String province = ""; + private String adCode = ""; + private String locationDetail = ""; + private String poiName = ""; + private String aoiName = ""; + private int errCode = 0; + private String errInfo = ""; + private String streetNum = ""; + private String description = ""; + private String buildingId = ""; + private String floor = ""; + private int gpsAccuracyStatus = 0; + private int satellite = 0; + + public float getBearing() { + return bearing; + } + + public void setBearing( float bearing ) { + this.bearing = bearing; + } + + public float getAccuracy() { + return accuracy; + } + + public void setAccuracy( float accuracy ) { + this.accuracy = accuracy; + } + + public String getProvider() { + return provider; + } + + public void setProvider( String provider ) { + this.provider = provider; + } + + public float getSpeed() { + return speed; + } + + public void setSpeed( float speed ) { + this.speed = speed; + } + + public String getLocationDetail() { + return locationDetail; + } + + public void setLocationDetail( String locationDetail ) { + this.locationDetail = locationDetail; + } + + public String getPoiName() { + return poiName; + } + + public void setPoiName( String poiName ) { + this.poiName = poiName; + } + + public String getAoiName() { + return aoiName; + } + + public void setAoiName( String aoiName ) { + this.aoiName = aoiName; + } + + public int getErrCode() { + return errCode; + } + + public void setErrCode( int errCode ) { + this.errCode = errCode; + } + + public String getErrInfo() { + return errInfo; + } + + public void setErrInfo( String errInfo ) { + this.errInfo = errInfo; + } + + public String getStreetNum() { + return streetNum; + } + + public void setStreetNum( String streetNum ) { + this.streetNum = streetNum; + } + + public String getDescription() { + return description; + } + + public void setDescription( String description ) { + this.description = description; + } + + public String getBuildingId() { + return buildingId; + } + + public void setBuildingId( String buildingId ) { + this.buildingId = buildingId; + } + + public String getFloor() { + return floor; + } + + public void setFloor( String floor ) { + this.floor = floor; + } + + public int getGpsAccuracyStatus() { + return gpsAccuracyStatus; + } + + public void setGpsAccuracyStatus( int gpsAccuracyStatus ) { + this.gpsAccuracyStatus = gpsAccuracyStatus; + } + + public int getSatellite() { + return satellite; + } + + public void setSatellite( int satellite ) { + this.satellite = satellite; + } + + public MogoLocation() { + } + + @Override + public String toString() { + return "MogoLocation{" + + "locType=" + locType + + ", latitude=" + latitude + + ", longitude=" + longitude + + ", altitude=" + altitude + + ", time=" + time + + ", bearing=" + bearing + + ", accuracy=" + accuracy + + ", speed=" + speed + + ", cityName='" + cityName + '\'' + + ", cityCode='" + cityCode + '\'' + + ", provider='" + provider + '\'' + + ", address='" + address + '\'' + + ", district='" + district + '\'' + + ", province='" + province + '\'' + + ", adCode='" + adCode + '\'' + + ", locationDetail='" + locationDetail + '\'' + + ", poiName='" + poiName + '\'' + + ", aoiName='" + aoiName + '\'' + + ", errCode=" + errCode + + ", errInfo='" + errInfo + '\'' + + ", streetNum='" + streetNum + '\'' + + ", description='" + description + '\'' + + ", buildingId='" + buildingId + '\'' + + ", floor='" + floor + '\'' + + ", gpsAccuracyStatus=" + gpsAccuracyStatus + + ", satellite=" + satellite + + '}'; + } + + public double getAltitude() { + return altitude; + } + + public void setAltitude( double altitude ) { + this.altitude = altitude; + } + + public String getAddress() { + return address; + } + + public void setAddress( String address ) { + this.address = address; + } + + public String getDistrict() { + return district; + } + + public void setDistrict( String district ) { + this.district = district; + } + + public String getProvince() { + return province; + } + + public void setProvince( String province ) { + this.province = province; + } + + public String getAdCode() { + return adCode; + } + + public void setAdCode( String adCode ) { + this.adCode = adCode; + } + + public int getLocType() { + return locType; + } + + public void setLocType( int locType ) { + this.locType = locType; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude( double latitude ) { + this.latitude = latitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude( double longitude ) { + this.longitude = longitude; + } + + public long getTime() { + return time; + } + + public void setTime( long time ) { + this.time = time; + } + + public String getCityCode() { + return cityCode; + } + + public void setCityCode( String cityCode ) { + this.cityCode = cityCode; + } + + public String getCityName() { + return cityName; + } + + public void setCityName( String cityName ) { + this.cityName = cityName; + } + + @Override + public MogoLocation clone() { + try { + return ( MogoLocation ) super.clone(); + } catch ( CloneNotSupportedException e ) { + e.printStackTrace(); + } + return this; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeInt( this.locType ); + dest.writeDouble( this.latitude ); + dest.writeDouble( this.longitude ); + dest.writeDouble( this.altitude ); + dest.writeLong( this.time ); + dest.writeFloat( this.bearing ); + dest.writeFloat( this.accuracy ); + dest.writeFloat( this.speed ); + dest.writeString( this.cityName ); + dest.writeString( this.cityCode ); + dest.writeString( this.provider ); + dest.writeString( this.address ); + dest.writeString( this.district ); + dest.writeString( this.province ); + dest.writeString( this.adCode ); + dest.writeString( this.locationDetail ); + dest.writeString( this.poiName ); + dest.writeString( this.aoiName ); + dest.writeInt( this.errCode ); + dest.writeString( this.errInfo ); + dest.writeString( this.streetNum ); + dest.writeString( this.description ); + dest.writeString( this.buildingId ); + dest.writeString( this.floor ); + dest.writeInt( this.gpsAccuracyStatus ); + dest.writeInt( this.satellite ); + } + + protected MogoLocation( Parcel in ) { + this.locType = in.readInt(); + this.latitude = in.readDouble(); + this.longitude = in.readDouble(); + this.altitude = in.readDouble(); + this.time = in.readLong(); + this.bearing = in.readFloat(); + this.accuracy = in.readFloat(); + this.speed = in.readFloat(); + this.cityName = in.readString(); + this.cityCode = in.readString(); + this.provider = in.readString(); + this.address = in.readString(); + this.district = in.readString(); + this.province = in.readString(); + this.adCode = in.readString(); + this.locationDetail = in.readString(); + this.poiName = in.readString(); + this.aoiName = in.readString(); + this.errCode = in.readInt(); + this.errInfo = in.readString(); + this.streetNum = in.readString(); + this.description = in.readString(); + this.buildingId = in.readString(); + this.floor = in.readString(); + this.gpsAccuracyStatus = in.readInt(); + this.satellite = in.readInt(); + } + + public static final Parcelable.Creator< MogoLocation > CREATOR = new Parcelable.Creator< MogoLocation >() { + @Override + public MogoLocation createFromParcel( Parcel source ) { + return new MogoLocation( source ); + } + + @Override + public MogoLocation[] newArray( int size ) { + return new MogoLocation[size]; + } + }; +} \ No newline at end of file diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocationClient.java b/libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocationClient.java new file mode 100644 index 0000000000..181444ada4 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/location/MogoLocationClient.java @@ -0,0 +1,75 @@ +package com.mogo.map.location; + +import android.content.Context; + +import com.mogo.map.amap.location.LocationClient; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ */ +public class MogoLocationClient implements IMogoLocationClient { + + private static volatile MogoLocationClient sInstance; + + private MogoLocationClient( Context context ) { + mDelegate = LocationClient.getInstance( context ); + } + + public static MogoLocationClient getInstance( Context context ) { + if ( sInstance == null ) { + synchronized ( MogoLocationClient.class ) { + if ( sInstance == null ) { + sInstance = new MogoLocationClient( context ); + } + } + } + return sInstance; + } + + private IMogoLocationClient mDelegate; + + @Override + public void start() { + if ( mDelegate != null ) { + mDelegate.start(); + } + } + + @Override + public void start( long interval ) { + if ( mDelegate != null ) { + mDelegate.start( interval ); + } + } + + @Override + public void stop() { + if ( mDelegate != null ) { + mDelegate.stop(); + } + } + + @Override + public void addLocationListener( ILocationListener listener ) { + if ( mDelegate != null ) { + mDelegate.addLocationListener( listener ); + } + } + + @Override + public void removeLocationListener( ILocationListener listener ) { + if ( mDelegate != null ) { + mDelegate.removeLocationListener( listener ); + } + } + + @Override + public MogoLocation getLastKnowLocation() { + if ( mDelegate != null ) { + return mDelegate.getLastKnowLocation(); + } + return null; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/marker/IMogoMarker.java b/libraries/mogo-map/src/main/java/com/mogo/map/marker/IMogoMarker.java new file mode 100644 index 0000000000..b6ba461919 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/marker/IMogoMarker.java @@ -0,0 +1,136 @@ +package com.mogo.map.marker; + +import android.graphics.Bitmap; + +import java.util.ArrayList; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图 marker 抽象 + */ +public interface IMogoMarker { + + /** + * 删除当前marker并销毁Marker的图片等资源 + */ + void destroy(); + + /** + * 删除当前marker。 + */ + void remove(); + + /** + * 隐藏Marker覆盖物的信息窗口。 + */ + void hideInfoWindow(); + + /** + * 设置Marker覆盖物的透明度 + * + * @param alpha + */ + void setAlpha( float alpha ); + + /** + * 设置Marker覆盖物的锚点比例。 + * + * @param anchorU + * @param anchorV + */ + void setAnchor( float anchorU, float anchorV ); + + /** + * 设置Marker覆盖物是否允许拖拽。 + * + * @param paramBoolean + */ + void setDraggable( boolean paramBoolean ); + + /** + * 设置 Marker覆盖物的图标 + * + * @param bitmap + */ + void setIcon( Bitmap bitmap ); + + /** + * 设置 Marker 的图标集合,相同图案的 icon 的 marker 最好使用同一个 BitmapDescriptor 对象以节省内存空间。 + * + * @param icons + */ + void setIcons( ArrayList< Bitmap > icons ); + + /** + * 设置Marker覆盖物的InfoWindow是否允许显示,默认为true + * 设置为false之后, 调用Marker.showInfoWindow() 将不会生效 + * + * @param enabled + */ + void setInfoWindowEnable( boolean enabled ); + + /** + * 设置Marker覆盖物的属性选项类 通过markerOption 给marker设置属性 + * + * @param opt + */ + void setMarkerOptions( MogoMarkerOptions opt ); + + void setObject( Object object ); + + /** + * 设置多少帧刷新一次图片资源,Marker动画的间隔时间,值越小动画越快。 + * + * @param period + */ + void setPeriod( int period ); + + void setPosition( double lat, double lng ); + + /** + * 设置Marker覆盖物图片旋转的角度,从正北开始,逆时针计算。 + * + * @param rotate + */ + void setRotateAngle( float rotate ); + + /** + * 设置Marker 覆盖物的文字片段。 + * + * @param snippet + */ + void setSnippet( String snippet ); + + /** + * 设置Marker 覆盖物的标题。 + * + * @param title + */ + void setTitle( String title ); + + /** + * 设置当前marker在最上面。 + */ + void setToTop(); + + /** + * 设置 Marker 覆盖物的可见属性。 + * + * @param visible + */ + void setVisible( boolean visible ); + + /** + * 设置Marker覆盖物的z轴值。 + * + * @param zIndex + */ + void setZIndex( float zIndex ); + + /** + * 显示 Marker 覆盖物的信息窗口。 + */ + void showInfoWindow(); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarker.java b/libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarker.java new file mode 100644 index 0000000000..978642da00 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarker.java @@ -0,0 +1,10 @@ +package com.mogo.map.marker; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * 描述 + */ +public class MogoMarker { +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarkerOptions.java b/libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarkerOptions.java new file mode 100644 index 0000000000..7b55d96847 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/marker/MogoMarkerOptions.java @@ -0,0 +1,222 @@ +package com.mogo.map.marker; + +import android.graphics.Bitmap; + +import java.util.ArrayList; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图marker + */ +public class MogoMarkerOptions { + + private double latitude; + private double longitude; + // 设置 Marker覆盖物 的标题 + private String title; + // 设置 Marker覆盖物的 文字描述 + private String snippet; + // 设置Marker覆盖物的图标。 + private Bitmap icon; + // 设置Marker覆盖物的动画帧图标列表,多张图片模拟gif的效果。 + private ArrayList< Bitmap > icons; + // 设置多少帧刷新一次图片资源,Marker动画的间隔时间,值越小动画越快。 + private int period = 20; + // 设置Marker覆盖物的图片旋转角度,从正北开始,逆时针计算。 + private float rotate; + // 设置Marker覆盖物是否平贴地图。 + private boolean flat = false; + // 设置Marker覆盖物是否可见。 + private boolean visible = true; + // 设置Marker覆盖物的InfoWindow是否允许显示 + private boolean inifoWindowEnable = true; + // 设置Marker覆盖物的透明度 + private float alpha = 1.0f; + + // Marker覆盖物的坐标是否是Gps + private boolean isGps = false; + + // Marker覆盖物锚点在水平范围的比例。 + private float u = 0.5f; + // Marker覆盖物锚点垂直范围的比例。 + private float v = 0.5f; + + // 设置Marker覆盖物是否可拖拽。 + private boolean draggable = false; + + // Marker覆盖物的InfoWindow相对X 轴的Marker的偏移 + private int offsetX = 0; + // Marker覆盖物的InfoWindow相对Y 轴的Marker的偏移 + private int offsetY = 0; + + // 设置Marker覆盖物 zIndex。 + private float zIndex = 0.0f; + + public MogoMarkerOptions latitude( double latitude ) { + this.latitude = latitude; + return this; + } + + public MogoMarkerOptions longitude( double longitude ) { + this.longitude = longitude; + return this; + } + + public MogoMarkerOptions title( String title ) { + this.title = title; + return this; + } + + public MogoMarkerOptions snippet( String snippet ) { + this.snippet = snippet; + return this; + } + + public MogoMarkerOptions icon( Bitmap icon ) { + this.icon = icon; + return this; + } + + public MogoMarkerOptions icons( ArrayList< Bitmap > icons ) { + this.icons = icons; + return this; + } + + public MogoMarkerOptions period( int period ) { + this.period = period; + if ( this.period < 1 ) { + this.period = 1; + } + return this; + } + + public MogoMarkerOptions rotate( float rotate ) { + this.rotate = rotate; + return this; + } + + public MogoMarkerOptions flat( boolean flat ) { + this.flat = flat; + return this; + } + + public MogoMarkerOptions visible( boolean visible ) { + this.visible = visible; + return this; + } + + public MogoMarkerOptions inifoWindowEnable( boolean inifoWindowEnable ) { + this.inifoWindowEnable = inifoWindowEnable; + return this; + } + + public MogoMarkerOptions alpha( float alpha ) { + this.alpha = alpha; + return this; + } + + public MogoMarkerOptions gps( boolean gps ) { + isGps = gps; + return this; + } + + public MogoMarkerOptions anchor( float u, float v ) { + this.u = u; + this.v = v; + return this; + } + + public MogoMarkerOptions draggable( boolean draggable ) { + this.draggable = draggable; + return this; + } + + public MogoMarkerOptions setInfoWindowOffset( int offsetX, int offsetY ) { + this.offsetX = offsetX; + this.offsetY = offsetY; + return this; + } + + public MogoMarkerOptions zIndex( float zIndex ) { + this.zIndex = zIndex; + return this; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + public String getTitle() { + return title; + } + + public String getSnippet() { + return snippet; + } + + public Bitmap getIcon() { + return icon; + } + + public ArrayList< Bitmap > getIcons() { + return icons; + } + + public int getPeriod() { + return period; + } + + public float getRotate() { + return rotate; + } + + public boolean isFlat() { + return flat; + } + + public boolean isVisible() { + return visible; + } + + public boolean isInifoWindowEnable() { + return inifoWindowEnable; + } + + public float getAlpha() { + return alpha; + } + + public boolean isGps() { + return isGps; + } + + public float getU() { + return u; + } + + public float getV() { + return v; + } + + public boolean isDraggable() { + return draggable; + } + + public int getOffsetX() { + return offsetX; + } + + public int getOffsetY() { + return offsetY; + } + + public float getzIndex() { + return zIndex; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearch.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearch.java new file mode 100644 index 0000000000..1a0ec205d3 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearch.java @@ -0,0 +1,55 @@ +package com.mogo.map.search; + +import com.mogo.map.exception.MogoMapException; +import com.mogo.map.search.query.MogoGeocodeQuery; +import com.mogo.map.search.query.MogoRegeocodeQuery; + +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 地理/逆地理位置搜索 + */ +public interface IMogoGeoSearch { + + /** + * 添加异步编码回调 + * + * @param listener + */ + void setGeoSearchListener( IMogoGeoSearchListener listener ); + + /** + * 同步获取逆地理编码地址 + * + * @param query + * @return + * @throws MogoMapException + */ + MogoRegeocodeAddress getFromLocation( MogoRegeocodeQuery query ) throws MogoMapException; + + /** + * 同步获取地理编码地址列表 + * + * @param query + * @return + * @throws MogoMapException + */ + List< MogoGeocodeAddress > getFromLocationName( MogoGeocodeQuery query ) throws MogoMapException; + + /** + * 异步获取逆地理编码 + * + * @param query + */ + void getFromLocationAsyn( MogoRegeocodeQuery query ); + + /** + * 同步获取地理编码回调 + * + * @param query + */ + void getFromLocationNameAsyn( MogoGeocodeQuery query ); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearchListener.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearchListener.java new file mode 100644 index 0000000000..ae1689b236 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoGeoSearchListener.java @@ -0,0 +1,26 @@ +package com.mogo.map.search; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 逆地理/地理编码回调 + */ +public interface IMogoGeoSearchListener { + + /** + * 逆地理编码(根据经纬度获取地理位置信息) + * + * @param regeocodeResult + * @param rCode + */ + void onRegeocodeSearched( MogoRegeocodeResult regeocodeResult, int rCode ); + + /** + * 根据名称和城市获取地理位置信息 + * + * @param geocodeResult + * @param i + */ + void onGeocodeSearched( MogoGeocodeResult geocodeResult, int i ); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsListener.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsListener.java new file mode 100644 index 0000000000..1cba147ba2 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsListener.java @@ -0,0 +1,14 @@ +package com.mogo.map.search; + +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * 描述 + */ +public interface IMogoInputtipsListener { + + void onGetInputtips( List< MogoTip > result, int code ); +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsSearch.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsSearch.java new file mode 100644 index 0000000000..0d7701e90d --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/IMogoInputtipsSearch.java @@ -0,0 +1,24 @@ +package com.mogo.map.search; + +import com.mogo.map.exception.MogoMapException; +import com.mogo.map.search.query.MogoInputtipsQuery; + +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * 描述 + */ +public interface IMogoInputtipsSearch { + + void setQuery( MogoInputtipsQuery query ); + + void setInputtipsListener( IMogoInputtipsListener listener ); + + void requestInputtipsAsyn(); + + List< MogoTip > requestInputtips() throws MogoMapException; +} + diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoAoiItem.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoAoiItem.java new file mode 100644 index 0000000000..e02c987202 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoAoiItem.java @@ -0,0 +1,99 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoAoiItem implements Parcelable { + + private String aoiId; + private String aoiName; + private String adCode; + private MogoLatLng aoiCenterPoint; + private float aoiArea; + + public String getAoiId() { + return aoiId; + } + + public void setAoiId( String aoiId ) { + this.aoiId = aoiId; + } + + public String getAoiName() { + return aoiName; + } + + public void setAoiName( String aoiName ) { + this.aoiName = aoiName; + } + + public String getAdCode() { + return adCode; + } + + public void setAdCode( String adCode ) { + this.adCode = adCode; + } + + public MogoLatLng getAoiCenterPoint() { + return aoiCenterPoint; + } + + public void setAoiCenterPoint( MogoLatLng aoiCenterPoint ) { + this.aoiCenterPoint = aoiCenterPoint; + } + + public float getAoiArea() { + return aoiArea; + } + + public void setAoiArea( float aoiArea ) { + this.aoiArea = aoiArea; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.aoiId ); + dest.writeString( this.aoiName ); + dest.writeString( this.adCode ); + dest.writeParcelable( this.aoiCenterPoint, flags ); + dest.writeFloat( this.aoiArea ); + } + + public MogoAoiItem() { + } + + protected MogoAoiItem( Parcel in ) { + this.aoiId = in.readString(); + this.aoiName = in.readString(); + this.adCode = in.readString(); + this.aoiCenterPoint = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.aoiArea = in.readFloat(); + } + + public static final Parcelable.Creator< MogoAoiItem > CREATOR = new Parcelable.Creator< MogoAoiItem >() { + @Override + public MogoAoiItem createFromParcel( Parcel source ) { + return new MogoAoiItem( source ); + } + + @Override + public MogoAoiItem[] newArray( int size ) { + return new MogoAoiItem[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoBusinessArea.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoBusinessArea.java new file mode 100644 index 0000000000..b3000a053d --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoBusinessArea.java @@ -0,0 +1,66 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoBusinessArea implements Parcelable { + + private MogoLatLng centerPoint; + private String name; + + public MogoLatLng getCenterPoint() { + return centerPoint; + } + + public void setCenterPoint( MogoLatLng centerPoint ) { + this.centerPoint = centerPoint; + } + + public String getName() { + return name; + } + + public void setName( String name ) { + this.name = name; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeParcelable( this.centerPoint, flags ); + dest.writeString( this.name ); + } + + public MogoBusinessArea() { + } + + protected MogoBusinessArea( Parcel in ) { + this.centerPoint = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.name = in.readString(); + } + + public static final Parcelable.Creator< MogoBusinessArea > CREATOR = new Parcelable.Creator< MogoBusinessArea >() { + @Override + public MogoBusinessArea createFromParcel( Parcel source ) { + return new MogoBusinessArea( source ); + } + + @Override + public MogoBusinessArea[] newArray( int size ) { + return new MogoBusinessArea[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoCrossroad.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoCrossroad.java new file mode 100644 index 0000000000..0fbc560d63 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoCrossroad.java @@ -0,0 +1,108 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoCrossroad implements Parcelable { + + private float distance; + private String direction; + private String firstRoadId; + private String firstRoadName; + private String secondRoadId; + private String secondRoadName; + + public float getDistance() { + return distance; + } + + public void setDistance( float distance ) { + this.distance = distance; + } + + public String getDirection() { + return direction; + } + + public void setDirection( String direction ) { + this.direction = direction; + } + + public String getFirstRoadId() { + return firstRoadId; + } + + public void setFirstRoadId( String firstRoadId ) { + this.firstRoadId = firstRoadId; + } + + public String getFirstRoadName() { + return firstRoadName; + } + + public void setFirstRoadName( String firstRoadName ) { + this.firstRoadName = firstRoadName; + } + + public String getSecondRoadId() { + return secondRoadId; + } + + public void setSecondRoadId( String secondRoadId ) { + this.secondRoadId = secondRoadId; + } + + public String getSecondRoadName() { + return secondRoadName; + } + + public void setSecondRoadName( String secondRoadName ) { + this.secondRoadName = secondRoadName; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeFloat( this.distance ); + dest.writeString( this.direction ); + dest.writeString( this.firstRoadId ); + dest.writeString( this.firstRoadName ); + dest.writeString( this.secondRoadId ); + dest.writeString( this.secondRoadName ); + } + + public MogoCrossroad() { + } + + protected MogoCrossroad( Parcel in ) { + this.distance = in.readFloat(); + this.direction = in.readString(); + this.firstRoadId = in.readString(); + this.firstRoadName = in.readString(); + this.secondRoadId = in.readString(); + this.secondRoadName = in.readString(); + } + + public static final Parcelable.Creator< MogoCrossroad > CREATOR = new Parcelable.Creator< MogoCrossroad >() { + @Override + public MogoCrossroad createFromParcel( Parcel source ) { + return new MogoCrossroad( source ); + } + + @Override + public MogoCrossroad[] newArray( int size ) { + return new MogoCrossroad[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeoSearch.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeoSearch.java new file mode 100644 index 0000000000..6d7c509b61 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeoSearch.java @@ -0,0 +1,62 @@ +package com.mogo.map.search; + +import android.content.Context; + +import com.mogo.map.amap.search.GeocodeSearchClient; +import com.mogo.map.exception.MogoMapException; +import com.mogo.map.search.query.MogoGeocodeQuery; +import com.mogo.map.search.query.MogoRegeocodeQuery; + +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 地理/逆地理位置搜索 + */ +public class MogoGeoSearch implements IMogoGeoSearch { + + private IMogoGeoSearch mDelegate; + + public MogoGeoSearch( Context context ) { + mDelegate = new GeocodeSearchClient( context ); + } + + @Override + public void setGeoSearchListener( IMogoGeoSearchListener listener ) { + if ( mDelegate != null ) { + mDelegate.setGeoSearchListener( listener ); + } + } + + @Override + public MogoRegeocodeAddress getFromLocation( MogoRegeocodeQuery query ) throws MogoMapException { + if ( mDelegate != null ) { + return mDelegate.getFromLocation( query ); + } + return null; + } + + @Override + public List< MogoGeocodeAddress > getFromLocationName( MogoGeocodeQuery query ) throws MogoMapException { + if ( mDelegate != null ) { + return mDelegate.getFromLocationName( query ); + } + return null; + } + + @Override + public void getFromLocationAsyn( MogoRegeocodeQuery query ) { + if ( mDelegate != null ) { + mDelegate.getFromLocationAsyn( query ); + } + } + + @Override + public void getFromLocationNameAsyn( MogoGeocodeQuery query ) { + if ( mDelegate != null ) { + mDelegate.getFromLocationNameAsyn( query ); + } + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeAddress.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeAddress.java new file mode 100644 index 0000000000..09d8700811 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeAddress.java @@ -0,0 +1,103 @@ +package com.mogo.map.search; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * geo 搜索位置信息 + */ +public class MogoGeocodeAddress { + + private String formatAddress; + private String province; + private String city; + private String district; + private String township; + private String neighborhood; + private String building; + private String adcode; + private MogoLatLng latlng; + private String level; + + public String getFormatAddress() { + return formatAddress; + } + + public void setFormatAddress( String formatAddress ) { + this.formatAddress = formatAddress; + } + + public String getProvince() { + return province; + } + + public void setProvince( String province ) { + this.province = province; + } + + public String getCity() { + return city; + } + + public void setCity( String city ) { + this.city = city; + } + + public String getDistrict() { + return district; + } + + public void setDistrict( String district ) { + this.district = district; + } + + public String getTownship() { + return township; + } + + public void setTownship( String township ) { + this.township = township; + } + + public String getNeighborhood() { + return neighborhood; + } + + public void setNeighborhood( String neighborhood ) { + this.neighborhood = neighborhood; + } + + public String getBuilding() { + return building; + } + + public void setBuilding( String building ) { + this.building = building; + } + + public String getAdcode() { + return adcode; + } + + public void setAdcode( String adcode ) { + this.adcode = adcode; + } + + public MogoLatLng getLatlng() { + return latlng; + } + + public void setLatlng( MogoLatLng latlng ) { + this.latlng = latlng; + } + + public String getLevel() { + return level; + } + + public void setLevel( String level ) { + this.level = level; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeResult.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeResult.java new file mode 100644 index 0000000000..ecc855c190 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoGeocodeResult.java @@ -0,0 +1,23 @@ +package com.mogo.map.search; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 地理编码搜索结果 + */ +public class MogoGeocodeResult { + + private List< MogoGeocodeAddress > addresses = new ArrayList<>(); + + public List< MogoGeocodeAddress > getAddresses() { + return addresses; + } + + public void setAddresses( List< MogoGeocodeAddress > addresses ) { + this.addresses = addresses; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoIndoorData.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoIndoorData.java new file mode 100644 index 0000000000..69ffd1200a --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoIndoorData.java @@ -0,0 +1,75 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoIndoorData implements Parcelable { + + private String poiId; + private int floor; + private String floorName; + + public String getPoiId() { + return poiId; + } + + public void setPoiId( String poiId ) { + this.poiId = poiId; + } + + public int getFloor() { + return floor; + } + + public void setFloor( int floor ) { + this.floor = floor; + } + + public String getFloorName() { + return floorName; + } + + public void setFloorName( String floorName ) { + this.floorName = floorName; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.poiId ); + dest.writeInt( this.floor ); + dest.writeString( this.floorName ); + } + + public MogoIndoorData() { + } + + protected MogoIndoorData( Parcel in ) { + this.poiId = in.readString(); + this.floor = in.readInt(); + this.floorName = in.readString(); + } + + public static final Parcelable.Creator< MogoIndoorData > CREATOR = new Parcelable.Creator< MogoIndoorData >() { + @Override + public MogoIndoorData createFromParcel( Parcel source ) { + return new MogoIndoorData( source ); + } + + @Override + public MogoIndoorData[] newArray( int size ) { + return new MogoIndoorData[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoInputtipsSearch.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoInputtipsSearch.java new file mode 100644 index 0000000000..483889b905 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoInputtipsSearch.java @@ -0,0 +1,53 @@ +package com.mogo.map.search; + +import android.content.Context; + +import com.mogo.map.amap.search.InputtipsSearch; +import com.mogo.map.exception.MogoMapException; +import com.mogo.map.search.query.MogoInputtipsQuery; + +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * 描述 + */ +public class MogoInputtipsSearch implements IMogoInputtipsSearch { + + private IMogoInputtipsSearch mDelegate; + + public MogoInputtipsSearch( Context context, MogoInputtipsQuery query ) { + mDelegate = new InputtipsSearch( context, query ); + } + + @Override + public void setQuery( MogoInputtipsQuery query ) { + if ( mDelegate != null ) { + mDelegate.setQuery( query ); + } + } + + @Override + public void setInputtipsListener( IMogoInputtipsListener listener ) { + if ( mDelegate != null ) { + mDelegate.setInputtipsListener( listener ); + } + } + + @Override + public void requestInputtipsAsyn() { + if ( mDelegate != null ) { + mDelegate.requestInputtipsAsyn(); + } + } + + @Override + public List< MogoTip > requestInputtips() throws MogoMapException { + if ( mDelegate != null ) { + return mDelegate.requestInputtips(); + } + return null; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPhoto.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPhoto.java new file mode 100644 index 0000000000..feffddf23f --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPhoto.java @@ -0,0 +1,64 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoPhoto implements Parcelable { + + private String title; + private String url; + + public String getTitle() { + return title; + } + + public void setTitle( String title ) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl( String url ) { + this.url = url; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.title ); + dest.writeString( this.url ); + } + + public MogoPhoto() { + } + + protected MogoPhoto( Parcel in ) { + this.title = in.readString(); + this.url = in.readString(); + } + + public static final Parcelable.Creator< MogoPhoto > CREATOR = new Parcelable.Creator< MogoPhoto >() { + @Override + public MogoPhoto createFromParcel( Parcel source ) { + return new MogoPhoto( source ); + } + + @Override + public MogoPhoto[] newArray( int size ) { + return new MogoPhoto[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItem.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItem.java new file mode 100644 index 0000000000..22de089a44 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItem.java @@ -0,0 +1,355 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoPoiItem implements Parcelable { + + private String businessArea; + private String adName; + private String cityName; + private String provinceName; + private String typeDes; + private String tel; + private String adCode; + private String poiId; + private int distance; + private String title; + private String snippet; + private MogoLatLng point; + private String cityCode; + private MogoLatLng enter; + private MogoLatLng exit; + private String website; + private String postcode; + private String email; + private String direction; + private boolean indoorMap; + private String provinceCode; + private String parkingType; + private List< MogoSubPoiItem > subPois = new ArrayList<>(); + private MogoIndoorData indoorData; + private List< MogoPhoto > photos = new ArrayList<>(); + private MogoPoiItemExtension poiExtension; + private String typeCode; + private String shopID; + + public String getBusinessArea() { + return businessArea; + } + + public void setBusinessArea( String businessArea ) { + this.businessArea = businessArea; + } + + public String getAdName() { + return adName; + } + + public void setAdName( String adName ) { + this.adName = adName; + } + + public String getCityName() { + return cityName; + } + + public void setCityName( String cityName ) { + this.cityName = cityName; + } + + public String getProvinceName() { + return provinceName; + } + + public void setProvinceName( String provinceName ) { + this.provinceName = provinceName; + } + + public String getTypeDes() { + return typeDes; + } + + public void setTypeDes( String typeDes ) { + this.typeDes = typeDes; + } + + public String getTel() { + return tel; + } + + public void setTel( String tel ) { + this.tel = tel; + } + + public String getAdCode() { + return adCode; + } + + public void setAdCode( String adCode ) { + this.adCode = adCode; + } + + public String getPoiId() { + return poiId; + } + + public void setPoiId( String poiId ) { + this.poiId = poiId; + } + + public int getDistance() { + return distance; + } + + public void setDistance( int distance ) { + this.distance = distance; + } + + public String getTitle() { + return title; + } + + public void setTitle( String title ) { + this.title = title; + } + + public String getSnippet() { + return snippet; + } + + public void setSnippet( String snippet ) { + this.snippet = snippet; + } + + public MogoLatLng getPoint() { + return point; + } + + public void setPoint( MogoLatLng point ) { + this.point = point; + } + + public String getCityCode() { + return cityCode; + } + + public void setCityCode( String cityCode ) { + this.cityCode = cityCode; + } + + public MogoLatLng getEnter() { + return enter; + } + + public void setEnter( MogoLatLng enter ) { + this.enter = enter; + } + + public MogoLatLng getExit() { + return exit; + } + + public void setExit( MogoLatLng exit ) { + this.exit = exit; + } + + public String getWebsite() { + return website; + } + + public void setWebsite( String website ) { + this.website = website; + } + + public String getPostcode() { + return postcode; + } + + public void setPostcode( String postcode ) { + this.postcode = postcode; + } + + public String getEmail() { + return email; + } + + public void setEmail( String email ) { + this.email = email; + } + + public String getDirection() { + return direction; + } + + public void setDirection( String direction ) { + this.direction = direction; + } + + public boolean isIndoorMap() { + return indoorMap; + } + + public void setIndoorMap( boolean indoorMap ) { + this.indoorMap = indoorMap; + } + + public String getProvinceCode() { + return provinceCode; + } + + public void setProvinceCode( String provinceCode ) { + this.provinceCode = provinceCode; + } + + public String getParkingType() { + return parkingType; + } + + public void setParkingType( String parkingType ) { + this.parkingType = parkingType; + } + + public List< MogoSubPoiItem > getSubPois() { + return subPois; + } + + public void setSubPois( List< MogoSubPoiItem > subPois ) { + this.subPois = subPois; + } + + public MogoIndoorData getIndoorData() { + return indoorData; + } + + public void setIndoorData( MogoIndoorData indoorData ) { + this.indoorData = indoorData; + } + + public List< MogoPhoto > getPhotos() { + return photos; + } + + public void setPhotos( List< MogoPhoto > photos ) { + this.photos = photos; + } + + public MogoPoiItemExtension getPoiExtension() { + return poiExtension; + } + + public void setPoiExtension( MogoPoiItemExtension poiExtension ) { + this.poiExtension = poiExtension; + } + + public String getTypeCode() { + return typeCode; + } + + public void setTypeCode( String typeCode ) { + this.typeCode = typeCode; + } + + public String getShopID() { + return shopID; + } + + public void setShopID( String shopID ) { + this.shopID = shopID; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.businessArea ); + dest.writeString( this.adName ); + dest.writeString( this.cityName ); + dest.writeString( this.provinceName ); + dest.writeString( this.typeDes ); + dest.writeString( this.tel ); + dest.writeString( this.adCode ); + dest.writeString( this.poiId ); + dest.writeInt( this.distance ); + dest.writeString( this.title ); + dest.writeString( this.snippet ); + dest.writeParcelable( this.point, flags ); + dest.writeString( this.cityCode ); + dest.writeParcelable( this.enter, flags ); + dest.writeParcelable( this.exit, flags ); + dest.writeString( this.website ); + dest.writeString( this.postcode ); + dest.writeString( this.email ); + dest.writeString( this.direction ); + dest.writeByte( this.indoorMap ? ( byte ) 1 : ( byte ) 0 ); + dest.writeString( this.provinceCode ); + dest.writeString( this.parkingType ); + dest.writeTypedList( this.subPois ); + dest.writeParcelable( this.indoorData, flags ); + dest.writeTypedList( this.photos ); + dest.writeParcelable( this.poiExtension, flags ); + dest.writeString( this.typeCode ); + dest.writeString( this.shopID ); + } + + public MogoPoiItem() { + } + + protected MogoPoiItem( Parcel in ) { + this.businessArea = in.readString(); + this.adName = in.readString(); + this.cityName = in.readString(); + this.provinceName = in.readString(); + this.typeDes = in.readString(); + this.tel = in.readString(); + this.adCode = in.readString(); + this.poiId = in.readString(); + this.distance = in.readInt(); + this.title = in.readString(); + this.snippet = in.readString(); + this.point = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.cityCode = in.readString(); + this.enter = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.exit = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.website = in.readString(); + this.postcode = in.readString(); + this.email = in.readString(); + this.direction = in.readString(); + this.indoorMap = in.readByte() != 0; + this.provinceCode = in.readString(); + this.parkingType = in.readString(); + this.subPois = in.createTypedArrayList( MogoSubPoiItem.CREATOR ); + this.indoorData = in.readParcelable( MogoIndoorData.class.getClassLoader() ); + this.photos = in.createTypedArrayList( MogoPhoto.CREATOR ); + this.poiExtension = in.readParcelable( MogoPoiItemExtension.class.getClassLoader() ); + this.typeCode = in.readString(); + this.shopID = in.readString(); + } + + public static final Creator< MogoPoiItem > CREATOR = new Creator< MogoPoiItem >() { + @Override + public MogoPoiItem createFromParcel( Parcel source ) { + return new MogoPoiItem( source ); + } + + @Override + public MogoPoiItem[] newArray( int size ) { + return new MogoPoiItem[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItemExtension.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItemExtension.java new file mode 100644 index 0000000000..645b91c6bb --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoPoiItemExtension.java @@ -0,0 +1,63 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoPoiItemExtension implements Parcelable { + + private String opentime; + private String rating; + + public String getOpentime() { + return opentime; + } + + public void setOpentime( String opentime ) { + this.opentime = opentime; + } + + public String getRating() { + return rating; + } + + public void setRating( String rating ) { + this.rating = rating; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.opentime ); + dest.writeString( this.rating ); + } + + public MogoPoiItemExtension() { + } + + protected MogoPoiItemExtension( Parcel in ) { + this.opentime = in.readString(); + this.rating = in.readString(); + } + + public static final Parcelable.Creator< MogoPoiItemExtension > CREATOR = new Parcelable.Creator< MogoPoiItemExtension >() { + @Override + public MogoPoiItemExtension createFromParcel( Parcel source ) { + return new MogoPoiItemExtension( source ); + } + + @Override + public MogoPoiItemExtension[] newArray( int size ) { + return new MogoPoiItemExtension[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeAddress.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeAddress.java new file mode 100644 index 0000000000..b3d90e6556 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeAddress.java @@ -0,0 +1,231 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 逆地理编码地址 + */ +public class MogoRegeocodeAddress implements Parcelable { + + private String formatAddress; + private String province; + private String city; + private String cityCode; + private String adCode; + private String district; + private String township; + private String neighborhood; + private String building; + private MogoStreetNumber streetNumber; + private List< MogoRegeocodeRoad > roads; + private List< MogoPoiItem > pois; + private List< MogoCrossroad > crossroads; + private List< MogoBusinessArea > businessAreas; + private List< MogoAoiItem > aois; + private String towncode; + private String country; + + public String getFormatAddress() { + return formatAddress; + } + + public void setFormatAddress( String formatAddress ) { + this.formatAddress = formatAddress; + } + + public String getProvince() { + return province; + } + + public void setProvince( String province ) { + this.province = province; + } + + public String getCity() { + return city; + } + + public void setCity( String city ) { + this.city = city; + } + + public String getCityCode() { + return cityCode; + } + + public void setCityCode( String cityCode ) { + this.cityCode = cityCode; + } + + public String getAdCode() { + return adCode; + } + + public void setAdCode( String adCode ) { + this.adCode = adCode; + } + + public String getDistrict() { + return district; + } + + public void setDistrict( String district ) { + this.district = district; + } + + public String getTownship() { + return township; + } + + public void setTownship( String township ) { + this.township = township; + } + + public String getNeighborhood() { + return neighborhood; + } + + public void setNeighborhood( String neighborhood ) { + this.neighborhood = neighborhood; + } + + public String getBuilding() { + return building; + } + + public void setBuilding( String building ) { + this.building = building; + } + + public MogoStreetNumber getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber( MogoStreetNumber streetNumber ) { + this.streetNumber = streetNumber; + } + + public List< MogoRegeocodeRoad > getRoads() { + return roads; + } + + public void setRoads( List< MogoRegeocodeRoad > roads ) { + this.roads = roads; + } + + public List< MogoPoiItem > getPois() { + return pois; + } + + public void setPois( List< MogoPoiItem > pois ) { + this.pois = pois; + } + + public List< MogoCrossroad > getCrossroads() { + return crossroads; + } + + public void setCrossroads( List< MogoCrossroad > crossroads ) { + this.crossroads = crossroads; + } + + public List< MogoBusinessArea > getBusinessAreas() { + return businessAreas; + } + + public void setBusinessAreas( List< MogoBusinessArea > businessAreas ) { + this.businessAreas = businessAreas; + } + + public List< MogoAoiItem > getAois() { + return aois; + } + + public void setAois( List< MogoAoiItem > aois ) { + this.aois = aois; + } + + public String getTowncode() { + return towncode; + } + + public void setTowncode( String towncode ) { + this.towncode = towncode; + } + + public String getCountry() { + return country; + } + + public void setCountry( String country ) { + this.country = country; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.formatAddress ); + dest.writeString( this.province ); + dest.writeString( this.city ); + dest.writeString( this.cityCode ); + dest.writeString( this.adCode ); + dest.writeString( this.district ); + dest.writeString( this.township ); + dest.writeString( this.neighborhood ); + dest.writeString( this.building ); + dest.writeParcelable( this.streetNumber, flags ); + dest.writeTypedList( this.roads ); + dest.writeTypedList( this.pois ); + dest.writeTypedList( this.crossroads ); + dest.writeTypedList( this.businessAreas ); + dest.writeTypedList( this.aois ); + dest.writeString( this.towncode ); + dest.writeString( this.country ); + } + + public MogoRegeocodeAddress() { + } + + protected MogoRegeocodeAddress( Parcel in ) { + this.formatAddress = in.readString(); + this.province = in.readString(); + this.city = in.readString(); + this.cityCode = in.readString(); + this.adCode = in.readString(); + this.district = in.readString(); + this.township = in.readString(); + this.neighborhood = in.readString(); + this.building = in.readString(); + this.streetNumber = in.readParcelable( MogoStreetNumber.class.getClassLoader() ); + this.roads = in.createTypedArrayList( MogoRegeocodeRoad.CREATOR ); + this.pois = in.createTypedArrayList( MogoPoiItem.CREATOR ); + this.crossroads = in.createTypedArrayList( MogoCrossroad.CREATOR ); + this.businessAreas = in.createTypedArrayList( MogoBusinessArea.CREATOR ); + this.aois = in.createTypedArrayList( MogoAoiItem.CREATOR ); + this.towncode = in.readString(); + this.country = in.readString(); + } + + public static final Parcelable.Creator< MogoRegeocodeAddress > CREATOR = new Parcelable.Creator< MogoRegeocodeAddress >() { + @Override + public MogoRegeocodeAddress createFromParcel( Parcel source ) { + return new MogoRegeocodeAddress( source ); + } + + @Override + public MogoRegeocodeAddress[] newArray( int size ) { + return new MogoRegeocodeAddress[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeResult.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeResult.java new file mode 100644 index 0000000000..6fd7141374 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeResult.java @@ -0,0 +1,53 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 逆地理编码结果 + */ +public class MogoRegeocodeResult implements Parcelable { + + private MogoRegeocodeAddress regeocodeAddress; + + public MogoRegeocodeAddress getRegeocodeAddress() { + return regeocodeAddress; + } + + public void setRegeocodeAddress( MogoRegeocodeAddress regeocodeAddress ) { + this.regeocodeAddress = regeocodeAddress; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeParcelable( this.regeocodeAddress, flags ); + } + + public MogoRegeocodeResult() { + } + + protected MogoRegeocodeResult( Parcel in ) { + this.regeocodeAddress = in.readParcelable( MogoRegeocodeAddress.class.getClassLoader() ); + } + + public static final Parcelable.Creator< MogoRegeocodeResult > CREATOR = new Parcelable.Creator< MogoRegeocodeResult >() { + @Override + public MogoRegeocodeResult createFromParcel( Parcel source ) { + return new MogoRegeocodeResult( source ); + } + + @Override + public MogoRegeocodeResult[] newArray( int size ) { + return new MogoRegeocodeResult[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeRoad.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeRoad.java new file mode 100644 index 0000000000..d3fb6c2ece --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoRegeocodeRoad.java @@ -0,0 +1,98 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoRegeocodeRoad implements Parcelable { + + private String id; + private String name; + private float distance; + private String direction; + private MogoLatLng point; + + public String getId() { + return id; + } + + public void setId( String id ) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName( String name ) { + this.name = name; + } + + public float getDistance() { + return distance; + } + + public void setDistance( float distance ) { + this.distance = distance; + } + + public String getDirection() { + return direction; + } + + public void setDirection( String direction ) { + this.direction = direction; + } + + public MogoLatLng getPoint() { + return point; + } + + public void setPoint( MogoLatLng point ) { + this.point = point; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.id ); + dest.writeString( this.name ); + dest.writeFloat( this.distance ); + dest.writeString( this.direction ); + dest.writeParcelable( this.point, flags ); + } + + public MogoRegeocodeRoad() { + } + + protected MogoRegeocodeRoad( Parcel in ) { + this.id = in.readString(); + this.name = in.readString(); + this.distance = in.readFloat(); + this.direction = in.readString(); + this.point = in.readParcelable( MogoLatLng.class.getClassLoader() ); + } + + public static final Parcelable.Creator< MogoRegeocodeRoad > CREATOR = new Parcelable.Creator< MogoRegeocodeRoad >() { + @Override + public MogoRegeocodeRoad createFromParcel( Parcel source ) { + return new MogoRegeocodeRoad( source ); + } + + @Override + public MogoRegeocodeRoad[] newArray( int size ) { + return new MogoRegeocodeRoad[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoStreetNumber.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoStreetNumber.java new file mode 100644 index 0000000000..c12d20fbaf --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoStreetNumber.java @@ -0,0 +1,98 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoStreetNumber implements Parcelable { + + private String street; + private String number; + private MogoLatLng latLonPoint; + private String direction; + private float distance; + + public String getStreet() { + return street; + } + + public void setStreet( String street ) { + this.street = street; + } + + public String getNumber() { + return number; + } + + public void setNumber( String number ) { + this.number = number; + } + + public MogoLatLng getLatLonPoint() { + return latLonPoint; + } + + public void setLatLonPoint( MogoLatLng latLonPoint ) { + this.latLonPoint = latLonPoint; + } + + public String getDirection() { + return direction; + } + + public void setDirection( String direction ) { + this.direction = direction; + } + + public float getDistance() { + return distance; + } + + public void setDistance( float distance ) { + this.distance = distance; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.street ); + dest.writeString( this.number ); + dest.writeParcelable( this.latLonPoint, flags ); + dest.writeString( this.direction ); + dest.writeFloat( this.distance ); + } + + public MogoStreetNumber() { + } + + protected MogoStreetNumber( Parcel in ) { + this.street = in.readString(); + this.number = in.readString(); + this.latLonPoint = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.direction = in.readString(); + this.distance = in.readFloat(); + } + + public static final Parcelable.Creator< MogoStreetNumber > CREATOR = new Parcelable.Creator< MogoStreetNumber >() { + @Override + public MogoStreetNumber createFromParcel( Parcel source ) { + return new MogoStreetNumber( source ); + } + + @Override + public MogoStreetNumber[] newArray( int size ) { + return new MogoStreetNumber[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoSubPoiItem.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoSubPoiItem.java new file mode 100644 index 0000000000..f1240ef0ee --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoSubPoiItem.java @@ -0,0 +1,121 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 描述 + */ +public class MogoSubPoiItem implements Parcelable { + + private String poiId; + private String title; + private String subName; + private int distance; + private MogoLatLng point; + private String snippet; + private String subTypeDes; + + public String getPoiId() { + return poiId; + } + + public void setPoiId( String poiId ) { + this.poiId = poiId; + } + + public String getTitle() { + return title; + } + + public void setTitle( String title ) { + this.title = title; + } + + public String getSubName() { + return subName; + } + + public void setSubName( String subName ) { + this.subName = subName; + } + + public int getDistance() { + return distance; + } + + public void setDistance( int distance ) { + this.distance = distance; + } + + public MogoLatLng getPoint() { + return point; + } + + public void setPoint( MogoLatLng point ) { + this.point = point; + } + + public String getSnippet() { + return snippet; + } + + public void setSnippet( String snippet ) { + this.snippet = snippet; + } + + public String getSubTypeDes() { + return subTypeDes; + } + + public void setSubTypeDes( String subTypeDes ) { + this.subTypeDes = subTypeDes; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.poiId ); + dest.writeString( this.title ); + dest.writeString( this.subName ); + dest.writeInt( this.distance ); + dest.writeParcelable( this.point, flags ); + dest.writeString( this.snippet ); + dest.writeString( this.subTypeDes ); + } + + public MogoSubPoiItem() { + } + + protected MogoSubPoiItem( Parcel in ) { + this.poiId = in.readString(); + this.title = in.readString(); + this.subName = in.readString(); + this.distance = in.readInt(); + this.point = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.snippet = in.readString(); + this.subTypeDes = in.readString(); + } + + public static final Parcelable.Creator< MogoSubPoiItem > CREATOR = new Parcelable.Creator< MogoSubPoiItem >() { + @Override + public MogoSubPoiItem createFromParcel( Parcel source ) { + return new MogoSubPoiItem( source ); + } + + @Override + public MogoSubPoiItem[] newArray( int size ) { + return new MogoSubPoiItem[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoTip.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoTip.java new file mode 100644 index 0000000000..1f6c631c2a --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/MogoTip.java @@ -0,0 +1,120 @@ +package com.mogo.map.search; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * inputtips 搜索结果 + */ +public class MogoTip implements Parcelable { + + private String poiID; + private MogoLatLng point; + private String name; + private String district; + private String adCode; + private String address; + private String typeCode; + + public String getPoiID() { + return poiID; + } + + public void setPoiID( String poiID ) { + this.poiID = poiID; + } + + public MogoLatLng getPoint() { + return point; + } + + public void setPoint( MogoLatLng point ) { + this.point = point; + } + + public String getName() { + return name; + } + + public void setName( String name ) { + this.name = name; + } + + public String getDistrict() { + return district; + } + + public void setDistrict( String district ) { + this.district = district; + } + + public String getAdCode() { + return adCode; + } + + public void setAdCode( String adCode ) { + this.adCode = adCode; + } + + public String getAddress() { + return address; + } + + public void setAddress( String address ) { + this.address = address; + } + + public String getTypeCode() { + return typeCode; + } + + public void setTypeCode( String typeCode ) { + this.typeCode = typeCode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.poiID ); + dest.writeParcelable( this.point, flags ); + dest.writeString( this.name ); + dest.writeString( this.district ); + dest.writeString( this.adCode ); + dest.writeString( this.address ); + dest.writeString( this.typeCode ); + } + + public MogoTip() { + } + + protected MogoTip( Parcel in ) { + this.poiID = in.readString(); + this.point = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.name = in.readString(); + this.district = in.readString(); + this.adCode = in.readString(); + this.address = in.readString(); + this.typeCode = in.readString(); + } + + public static final Parcelable.Creator< MogoTip > CREATOR = new Parcelable.Creator< MogoTip >() { + @Override + public MogoTip createFromParcel( Parcel source ) { + return new MogoTip( source ); + } + + @Override + public MogoTip[] newArray( int size ) { + return new MogoTip[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoGeocodeQuery.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoGeocodeQuery.java new file mode 100644 index 0000000000..441bd20bad --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoGeocodeQuery.java @@ -0,0 +1,63 @@ +package com.mogo.map.search.query; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 地理编码查询条件 + */ +public class MogoGeocodeQuery implements Parcelable { + private String locationName; + private String city; + + public String getLocationName() { + return locationName; + } + + public void setLocationName( String locationName ) { + this.locationName = locationName; + } + + public String getCity() { + return city; + } + + public void setCity( String city ) { + this.city = city; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeString( this.locationName ); + dest.writeString( this.city ); + } + + public MogoGeocodeQuery() { + } + + protected MogoGeocodeQuery( Parcel in ) { + this.locationName = in.readString(); + this.city = in.readString(); + } + + public static final Parcelable.Creator< MogoGeocodeQuery > CREATOR = new Parcelable.Creator< MogoGeocodeQuery >() { + @Override + public MogoGeocodeQuery createFromParcel( Parcel source ) { + return new MogoGeocodeQuery( source ); + } + + @Override + public MogoGeocodeQuery[] newArray( int size ) { + return new MogoGeocodeQuery[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoInputtipsQuery.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoInputtipsQuery.java new file mode 100644 index 0000000000..7267328d81 --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoInputtipsQuery.java @@ -0,0 +1,58 @@ +package com.mogo.map.search.query; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-20 + *

+ * inputtips 搜索条件 + */ +public class MogoInputtipsQuery { + + private String keyword; + private String city; + private String type; + private boolean cityLimit; + private MogoLatLng location; + + public String getKeyword() { + return keyword; + } + + public void setKeyword( String keyword ) { + this.keyword = keyword; + } + + public String getCity() { + return city; + } + + public void setCity( String city ) { + this.city = city; + } + + public String getType() { + return type; + } + + public void setType( String type ) { + this.type = type; + } + + public boolean isCityLimit() { + return cityLimit; + } + + public void setCityLimit( boolean cityLimit ) { + this.cityLimit = cityLimit; + } + + public MogoLatLng getLocation() { + return location; + } + + public void setLocation( MogoLatLng location ) { + this.location = location; + } +} diff --git a/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoRegeocodeQuery.java b/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoRegeocodeQuery.java new file mode 100644 index 0000000000..cd295d404a --- /dev/null +++ b/libraries/mogo-map/src/main/java/com/mogo/map/search/query/MogoRegeocodeQuery.java @@ -0,0 +1,88 @@ +package com.mogo.map.search.query; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.mogo.map.MogoLatLng; + +/** + * @author congtaowang + * @since 2019-12-19 + *

+ * 逆地理编码查询条件 + */ +public class MogoRegeocodeQuery implements Parcelable { + + private MogoLatLng point; + private float radius; + private String latlngType; + private String poiType; + + public MogoLatLng getPoint() { + return point; + } + + public void setPoint( MogoLatLng point ) { + this.point = point; + } + + public float getRadius() { + return radius; + } + + public void setRadius( float radius ) { + this.radius = radius; + } + + public String getLatlngType() { + return latlngType; + } + + public void setLatlngType( String latlngType ) { + this.latlngType = latlngType; + } + + public String getPoiType() { + return poiType; + } + + public void setPoiType( String poiType ) { + this.poiType = poiType; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( Parcel dest, int flags ) { + dest.writeParcelable( this.point, flags ); + dest.writeFloat( this.radius ); + dest.writeString( this.latlngType ); + dest.writeString( this.poiType ); + } + + public MogoRegeocodeQuery() { + } + + protected MogoRegeocodeQuery( Parcel in ) { + this.point = in.readParcelable( MogoLatLng.class.getClassLoader() ); + this.radius = in.readFloat(); + this.latlngType = in.readString(); + this.poiType = in.readString(); + } + + public static final Parcelable.Creator< MogoRegeocodeQuery > CREATOR = new Parcelable.Creator< MogoRegeocodeQuery >() { + @Override + public MogoRegeocodeQuery createFromParcel( Parcel source ) { + return new MogoRegeocodeQuery( source ); + } + + @Override + public MogoRegeocodeQuery[] newArray( int size ) { + return new MogoRegeocodeQuery[size]; + } + }; +} diff --git a/libraries/mogo-map/src/main/res/values/strings.xml b/libraries/mogo-map/src/main/res/values/strings.xml new file mode 100644 index 0000000000..27e89c2880 --- /dev/null +++ b/libraries/mogo-map/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + mogo-map + diff --git a/services/mogo-service-api/.gitignore b/services/mogo-service-api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/services/mogo-service-api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/services/mogo-service-api/build.gradle b/services/mogo-service-api/build.gradle new file mode 100644 index 0000000000..42f0f51704 --- /dev/null +++ b/services/mogo-service-api/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'androidx.appcompat:appcompat:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + implementation project(":libraries:mogo-map") + implementation rootProject.ext.dependencies.arouter + api rootProject.ext.dependencies.fresco +} diff --git a/services/mogo-service-api/consumer-rules.pro b/services/mogo-service-api/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/mogo-service-api/proguard-rules.pro b/services/mogo-service-api/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/services/mogo-service-api/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/services/mogo-service-api/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java b/services/mogo-service-api/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java new file mode 100644 index 0000000000..44238af9f8 --- /dev/null +++ b/services/mogo-service-api/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.mogo.service.map; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith( AndroidJUnit4.class ) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals( "com.mogo.service.amap.test", appContext.getPackageName() ); + } +} diff --git a/services/mogo-service-api/src/main/AndroidManifest.xml b/services/mogo-service-api/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..46f9830497 --- /dev/null +++ b/services/mogo-service-api/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/services/mogo-service-api/src/main/java/com/mogo/service/MogoServicePaths.java b/services/mogo-service-api/src/main/java/com/mogo/service/MogoServicePaths.java new file mode 100644 index 0000000000..fabd415899 --- /dev/null +++ b/services/mogo-service-api/src/main/java/com/mogo/service/MogoServicePaths.java @@ -0,0 +1,25 @@ +package com.mogo.service; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 对外服务模块路径 + *

+ *

+ * 使用方式: + *

+ * Arouter.getInstance().path("").navigate() + */ +public class MogoServicePaths { + + /** + * 地图服务接口路径 + */ + public static final String PATH_MAP_SERVICE = "/mogo/services/map"; + + /** + * 图片接口 + */ + public static final String PATH_IMAGE_LOADER = "/mogo/utils/imageloader"; +} diff --git a/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageLoaderListener.java b/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageLoaderListener.java new file mode 100644 index 0000000000..7a5bab5de9 --- /dev/null +++ b/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageLoaderListener.java @@ -0,0 +1,20 @@ +package com.mogo.service.imageloader; + +import android.graphics.Bitmap; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 图片下载接口 + */ +public interface IMogoImageLoaderListener { + + void onStart(); + +// void onProcess( int completedSize, int totalSize ); + + void onCompleted( Bitmap bitmap ); + + void onFailure( Exception e ); +} \ No newline at end of file diff --git a/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageloader.java b/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageloader.java new file mode 100644 index 0000000000..e03f59686c --- /dev/null +++ b/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/IMogoImageloader.java @@ -0,0 +1,28 @@ +package com.mogo.service.imageloader; + +import android.content.Context; + +import com.alibaba.android.arouter.facade.template.IProvider; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 图片接口 + */ +public interface IMogoImageloader extends IProvider { + + void init( Context context ); + + void displayImage( String url, MogoImageView imageView ); + + void displayImage( String url, MogoImageView imageView, int width, int height ); + + void displayImage( String url, MogoImageView imageView, int width, int height, final IMogoImageLoaderListener listener ); + + void displayImage( String url, MogoImageView imageView, final IMogoImageLoaderListener listener ); + + void downloadImage( Context context, String url, IMogoImageLoaderListener listener ); + + void destroy(); +} diff --git a/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/MogoImageView.java b/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/MogoImageView.java new file mode 100644 index 0000000000..1719b7e803 --- /dev/null +++ b/services/mogo-service-api/src/main/java/com/mogo/service/imageloader/MogoImageView.java @@ -0,0 +1,174 @@ +package com.mogo.service.imageloader; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Matrix; +import android.util.AttributeSet; + +import androidx.annotation.DrawableRes; + +import com.facebook.drawee.view.GenericDraweeView; +import com.mogo.service.R; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 封装 facebook GenericDraweeView,实现占位图、失败占位图、形状、边框、模糊等效果 + */ +public class MogoImageView extends GenericDraweeView { + + private int mPlaceHolder; + private int mFailureHolder; + private int mOverlayImageId; + private int mShape; + private int mRadius; + private int mTopLeftRadius; + private int mTopRightRadius; + private int mBottomLeftRadius; + private int mBottomRightRadius; + private int mBorderWidth; + private int mBorderColor; + private int mBlurRadius; + private boolean mIsBlur; + + public static final int SHAPE_NORMAL = 0; + public static final int SHAPE_CIRCLE = 1; + public static final int SHAPE_ROUND = 2; + + public MogoImageView( Context context ) { + this( context, null, 0 ); + } + + public MogoImageView( Context context, AttributeSet attrs ) { + this( context, attrs, 0 ); + } + + public MogoImageView( Context context, AttributeSet attrs, int theme ) { + super( context, attrs, theme ); + TypedArray arrays = context.obtainStyledAttributes( attrs, R.styleable.MogoImageView ); + mPlaceHolder = arrays.getResourceId( R.styleable.MogoImageView_placeHolder, 0 ); + mFailureHolder = arrays.getResourceId( R.styleable.MogoImageView_failureHolder, 0 ); + mOverlayImageId = arrays.getResourceId( R.styleable.MogoImageView_overlayImageId, 0 ); + mShape = arrays.getInt( R.styleable.MogoImageView_shape, SHAPE_NORMAL ); + mRadius = arrays.getDimensionPixelSize( R.styleable.MogoImageView_radius, 0 ); + mTopLeftRadius = arrays.getDimensionPixelSize( R.styleable.MogoImageView_topLeftRadius, 0 ); + mTopRightRadius = arrays.getDimensionPixelSize( R.styleable.MogoImageView_topRightRadius, 0 ); + mBottomLeftRadius = arrays.getDimensionPixelSize( R.styleable.MogoImageView_bottomLeftRadius, 0 ); + mBottomRightRadius = arrays.getDimensionPixelSize( R.styleable.MogoImageView_bottomRightRadius, 0 ); + mBorderWidth = arrays.getDimensionPixelSize( R.styleable.MogoImageView_shapeBorderWidth, 0 ); + mBorderColor = arrays.getColor( R.styleable.MogoImageView_borderColor, Color.WHITE ); + mIsBlur = arrays.getBoolean( R.styleable.MogoImageView_isBlur, false ); + mBlurRadius = arrays.getInt( R.styleable.MogoImageView_blurRadius, 25 ); + arrays.recycle(); + } + + public void setRadius( float radius ) { + this.mRadius = ( int ) radius; + } + + public void setRadius( int[] radius ) { + if ( radius != null && radius.length == 8 ) { + mTopLeftRadius = radius[0] >= radius[1] ? radius[0] : radius[1]; + mTopRightRadius = radius[2] >= radius[3] ? radius[2] : radius[3]; + mBottomRightRadius = radius[4] >= radius[5] ? radius[4] : radius[5]; + mBottomLeftRadius = radius[6] >= radius[7] ? radius[6] : radius[7]; + } + } + + public void setShape( int shape ) { + this.mShape = shape; + } + + public void setBorderWidth( int width ) { + this.mBorderWidth = width; + } + + public void setBorderColor( int color ) { + this.mBorderColor = color; + } + + public void setPlaceHolder( int placeHolder ) { + this.mPlaceHolder = placeHolder; + } + + public void setFailureHolder( int failureHolder ) { + this.mFailureHolder = failureHolder; + } + + public void setOverlayImageId( @DrawableRes int overlayImageId ) { + this.mOverlayImageId = overlayImageId; + } + + public void setBlur( boolean isBlur ) { + this.mIsBlur = isBlur; + } + + public void setBlurRadius( int blurRadius ) { + this.mBlurRadius = blurRadius; + } + + public int getPlaceHolder() { + return mPlaceHolder; + } + + public int getFailureHolder() { + return mFailureHolder; + } + + public int getOverlayImageId() { + return mOverlayImageId; + } + + public int getShape() { + return mShape; + } + + public float[] getRadii() { + if ( mTopLeftRadius != 0 || mTopRightRadius != 0 || mBottomRightRadius != 0 || mBottomLeftRadius != 0 ) { + float[] radii = {mTopLeftRadius, mTopLeftRadius, mTopRightRadius, mTopRightRadius, + mBottomRightRadius, mBottomRightRadius, mBottomLeftRadius, mBottomLeftRadius}; + return radii; + } + + return null; + } + + public int getRadius() { + return mRadius; + } + + public int getBorderWidth() { + return mBorderWidth; + } + + public int getBorderColor() { + return mBorderColor; + } + + public boolean isBlur() { + return mIsBlur; + } + + public int getBlurRadius() { + return mBlurRadius; + } + + public String getParams() { + StringBuilder builder = new StringBuilder(); + builder.append( mIsBlur ).append( mBlurRadius ) + .append( mShape ).append( mRadius ) + .append( mBorderWidth ).append( mBorderColor ); + return builder.toString(); + } + + /** + * 解决共享动画无效的问题 + * + * @param matrix + */ + public void animateTransform( Matrix matrix ) { + invalidate(); + } +} \ No newline at end of file diff --git a/services/mogo-service-api/src/main/java/com/mogo/service/map/IMogoMapService.java b/services/mogo-service-api/src/main/java/com/mogo/service/map/IMogoMapService.java new file mode 100644 index 0000000000..6334ef265a --- /dev/null +++ b/services/mogo-service-api/src/main/java/com/mogo/service/map/IMogoMapService.java @@ -0,0 +1,64 @@ +package com.mogo.service.map; + +import android.content.Context; + +import com.alibaba.android.arouter.facade.template.IProvider; +import com.mogo.map.location.IMogoLocationClient; +import com.mogo.map.marker.IMogoMarker; +import com.mogo.map.marker.MogoMarkerOptions; +import com.mogo.map.search.IMogoGeoSearch; +import com.mogo.map.search.IMogoInputtipsSearch; +import com.mogo.map.search.query.MogoInputtipsQuery; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图对外地接口 + */ +public interface IMogoMapService extends IProvider { + + /** + * 获取定位服务实例 + * + * @param context + * @return + */ + IMogoLocationClient getSingletonLocationClient( Context context ); + + /** + * 添加marker + * + * @param options + * @return + */ + IMogoMarker addMarker( MogoMarkerOptions options ); + + /** + * 添加多个marker + * + * @param options + * @return + */ + List< IMogoMarker > addMarkers( ArrayList< MogoMarkerOptions > options, boolean moveToCenter ); + + /** + * 获取关键字搜索地址服务 + * + * @param context + * @param query + * @return + */ + IMogoInputtipsSearch getInputtipsSearch( Context context, MogoInputtipsQuery query ); + + /** + * 地理编码或逆地理编码服务 + * + * @param context + * @return + */ + IMogoGeoSearch getGeoSearch( Context context ); +} diff --git a/services/mogo-service-api/src/main/res/values/attrs.xml b/services/mogo-service-api/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..febf72ba74 --- /dev/null +++ b/services/mogo-service-api/src/main/res/values/attrs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/services/mogo-service-api/src/main/res/values/strings.xml b/services/mogo-service-api/src/main/res/values/strings.xml new file mode 100644 index 0000000000..eb1356c817 --- /dev/null +++ b/services/mogo-service-api/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + amap-api + diff --git a/services/mogo-service-api/src/test/java/com/mogo/service/map/ExampleUnitTest.java b/services/mogo-service-api/src/test/java/com/mogo/service/map/ExampleUnitTest.java new file mode 100644 index 0000000000..6c7fa71433 --- /dev/null +++ b/services/mogo-service-api/src/test/java/com/mogo/service/map/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.mogo.service.map; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals( 4, 2 + 2 ); + } +} \ No newline at end of file diff --git a/services/mogo-service/.gitignore b/services/mogo-service/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/services/mogo-service/.gitignore @@ -0,0 +1 @@ +/build diff --git a/services/mogo-service/build.gradle b/services/mogo-service/build.gradle new file mode 100644 index 0000000000..463966d78e --- /dev/null +++ b/services/mogo-service/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + + + javaCompileOptions { + annotationProcessorOptions { + arguments = [AROUTER_MODULE_NAME: project.getName()] + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'androidx.appcompat:appcompat:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + implementation project(":libraries:mogo-map") + implementation project(":foudations:mogo-utils") + implementation project(':services:mogo-service-api') + implementation rootProject.ext.dependencies.arouter + annotationProcessor rootProject.ext.dependencies.aroutercompiler +} diff --git a/services/mogo-service/consumer-rules.pro b/services/mogo-service/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/mogo-service/proguard-rules.pro b/services/mogo-service/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/services/mogo-service/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/services/mogo-service/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java b/services/mogo-service/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java new file mode 100644 index 0000000000..44238af9f8 --- /dev/null +++ b/services/mogo-service/src/androidTest/java/com/mogo/service/map/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.mogo.service.map; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith( AndroidJUnit4.class ) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals( "com.mogo.service.amap.test", appContext.getPackageName() ); + } +} diff --git a/services/mogo-service/src/main/AndroidManifest.xml b/services/mogo-service/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..3b87b75ef7 --- /dev/null +++ b/services/mogo-service/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/MogoImageLoader.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/MogoImageLoader.java new file mode 100644 index 0000000000..798306bd7d --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/MogoImageLoader.java @@ -0,0 +1,69 @@ +package com.mogo.service.impl.imageloader; + +import android.content.Context; + +import com.alibaba.android.arouter.facade.annotation.Route; +import com.mogo.service.MogoServicePaths; +import com.mogo.service.imageloader.IMogoImageLoaderListener; +import com.mogo.service.imageloader.IMogoImageloader; +import com.mogo.service.imageloader.MogoImageView; +import com.mogo.service.impl.imageloader.glide.GlideImageLoader; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 实现图片接口 + */ +@Route( path = MogoServicePaths.PATH_IMAGE_LOADER ) +public class MogoImageLoader implements IMogoImageloader { + + private IMogoImageloader mDelegate; + + @Override + public void init( Context context ) { + mDelegate = GlideImageLoader.getInstance(); + } + + @Override + public void displayImage( String url, MogoImageView imageView ) { + if ( mDelegate != null ) { + mDelegate.displayImage( url, imageView ); + } + } + + @Override + public void displayImage( String url, MogoImageView imageView, int width, int height ) { + if ( mDelegate != null ) { + mDelegate.displayImage( url, imageView, width, height ); + } + } + + @Override + public void displayImage( String url, MogoImageView imageView, int width, int height, IMogoImageLoaderListener listener ) { + if ( mDelegate != null ) { + mDelegate.displayImage( url, imageView, width, height, listener ); + } + } + + @Override + public void displayImage( String url, MogoImageView imageView, IMogoImageLoaderListener listener ) { + if ( mDelegate != null ) { + mDelegate.displayImage( url, imageView, 0, 0, listener ); + } + } + + @Override + public void downloadImage( Context context, String url, IMogoImageLoaderListener listener ) { + if ( mDelegate != null ) { + mDelegate.downloadImage( context, url, listener ); + } + } + + @Override + public void destroy() { + if ( mDelegate != null ) { + mDelegate.destroy(); + } + } +} diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/GlideImageLoader.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/GlideImageLoader.java new file mode 100644 index 0000000000..a6a17fb32c --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/GlideImageLoader.java @@ -0,0 +1,169 @@ +package com.mogo.service.impl.imageloader.glide; + +import android.content.Context; +import android.graphics.Bitmap; + +import androidx.annotation.Nullable; + +import com.alibaba.android.arouter.facade.annotation.Route; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; +import com.mogo.service.MogoServicePaths; +import com.mogo.service.imageloader.IMogoImageLoaderListener; +import com.mogo.service.imageloader.IMogoImageloader; +import com.mogo.service.imageloader.MogoImageView; +import com.mogo.service.impl.imageloader.glide.transform.GlideBlurTransform; +import com.mogo.service.impl.imageloader.glide.transform.GlideCircleBitmapTransform; +import com.mogo.service.impl.imageloader.glide.transform.GlideRoundBitmapTransform; +import com.mogo.service.impl.imageloader.glide.utils.DiskLruCacheManager; +import com.mogo.utils.glide.GlideApp; + +/** + * @author congtaowang + * @since 2019-12-23 + *

+ * 实现图片接口 + */ +@Route( path = MogoServicePaths.PATH_IMAGE_LOADER ) +public class GlideImageLoader implements IMogoImageloader { + + private static volatile GlideImageLoader sInstance; + + private GlideImageLoader() { + } + + public static GlideImageLoader getInstance() { + if ( sInstance == null ) { + synchronized ( GlideImageLoader.class ) { + if ( sInstance == null ) { + sInstance = new GlideImageLoader(); + } + } + } + return sInstance; + } + + @Override + public void init( Context context ) { + + } + + @Override + public void displayImage( String url, MogoImageView imageView ) { + displayImage( url, imageView, null ); + } + + @Override + public void displayImage( String url, MogoImageView imageView, int width, int height ) { + displayImage( url, imageView, width, height, null ); + } + + @Override + public void displayImage( String url, MogoImageView imageView, IMogoImageLoaderListener listener ) { + displayImage( url, imageView, 0, 0, listener ); + } + + @Override + public void displayImage( String url, MogoImageView imageView, int width, int height, final IMogoImageLoaderListener listener ) { + + if ( listener != null ) { + listener.onStart(); + } + // 缓存的key + Context context = imageView.getContext().getApplicationContext(); + String key = url + imageView.getParams(); + Bitmap bitmap = DiskLruCacheManager.getInstance( context ).get( key ); + if ( bitmap != null && !bitmap.isRecycled() ) { + imageView.setImageBitmap( bitmap ); + if ( listener != null ) { + listener.onCompleted( bitmap ); + } + return; + } + + RequestOptions options = new RequestOptions() + .placeholder( imageView.getPlaceHolder() ) + .error( imageView.getFailureHolder() ) + .dontAnimate(); + + // 根据imageView的形状,设置相应的transform + boolean isBlur = imageView.isBlur(); + int shape = imageView.getShape(); + if ( isBlur ) { + if ( shape == MogoImageView.SHAPE_CIRCLE ) { + options.transforms( new GlideBlurTransform( context, key, imageView.getBlurRadius() ), + new GlideCircleBitmapTransform( context, key, imageView.getBorderWidth(), imageView.getBorderColor() ) ); + } else if ( shape == MogoImageView.SHAPE_ROUND ) { + options.transforms( new GlideBlurTransform( context, key, imageView.getBlurRadius() ), + new GlideRoundBitmapTransform( context, key, imageView.getRadius(), imageView.getBorderWidth(), imageView.getBorderColor() ) ); + } else { + options.transforms( new GlideBlurTransform( context, key, imageView.getBlurRadius() ) ); + } + } else { + if ( shape == MogoImageView.SHAPE_CIRCLE ) { + options.transforms( new GlideCircleBitmapTransform( context, key, imageView.getBorderWidth(), imageView.getBorderColor() ) ); + } else if ( shape == MogoImageView.SHAPE_ROUND ) { + options.transforms( new GlideRoundBitmapTransform( context, key, imageView.getRadius(), imageView.getBorderWidth(), imageView.getBorderColor() ) ); + } + } + + GlideApp.with( context ) + .asBitmap() + .load( url ) + .apply( options ) + .addListener( new RequestListener< Bitmap >() { + + @Override + public boolean onLoadFailed( @Nullable GlideException e, Object model, Target< Bitmap > target, boolean isFirstResource ) { + if ( listener != null ) { + listener.onFailure( e ); + } + return false; + } + + @Override + public boolean onResourceReady( Bitmap resource, Object model, Target< Bitmap > target, DataSource dataSource, boolean isFirstResource ) { + if ( listener != null ) { + listener.onCompleted( resource ); + } + return false; + } + } ) + .into( imageView ); + } + + @Override + public void downloadImage( Context context, String url, final IMogoImageLoaderListener listener ) { + if ( listener != null ) { + listener.onStart(); + } + GlideApp.with( context.getApplicationContext() ) + .asBitmap() + .load( url ) + .listener( new RequestListener< Bitmap >() { + + @Override + public boolean onLoadFailed( @Nullable GlideException e, Object model, Target< Bitmap > target, boolean isFirstResource ) { + if ( listener != null ) { + listener.onFailure( e ); + } + return false; + } + + @Override + public boolean onResourceReady( Bitmap resource, Object model, Target< Bitmap > target, DataSource dataSource, boolean isFirstResource ) { + if ( listener != null ) { + listener.onCompleted( resource ); + } + return false; + } + } ); + } + + @Override + public void destroy() { + } +} diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideBlurTransform.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideBlurTransform.java new file mode 100644 index 0000000000..65f1c49fbf --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideBlurTransform.java @@ -0,0 +1,39 @@ +package com.mogo.service.impl.imageloader.glide.transform; + +import android.content.Context; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; +import com.mogo.service.impl.imageloader.glide.utils.DiskLruCacheManager; +import com.mogo.service.impl.imageloader.glide.utils.FastBlurUtil; + +import java.security.MessageDigest; + +public class GlideBlurTransform extends BitmapTransformation { + + private String key; + private Context context; + private int blurRadius; + + public GlideBlurTransform( Context context, String key, int blurRadius ) { + this.context = context; + this.key = key; + this.blurRadius = blurRadius; + } + + @Override + protected Bitmap transform( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight ) { + Bitmap bitmap = FastBlurUtil.doBlur( toTransform, 8, blurRadius ); + // 缓存高斯模糊图片 + DiskLruCacheManager.getInstance( context ).put( key, bitmap ); + return bitmap; + } + + @Override + public void updateDiskCacheKey( MessageDigest messageDigest ) { + + } +} \ No newline at end of file diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideCircleBitmapTransform.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideCircleBitmapTransform.java new file mode 100644 index 0000000000..340b8d1627 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideCircleBitmapTransform.java @@ -0,0 +1,68 @@ + +package com.mogo.service.impl.imageloader.glide.transform; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; +import com.mogo.service.impl.imageloader.glide.utils.DiskLruCacheManager; + +import java.security.MessageDigest; + +public class GlideCircleBitmapTransform extends BitmapTransformation { + + private int mBorderWidth; + private int mBorderColor; + private String mKey; + private Context mContext; + + public GlideCircleBitmapTransform( Context context, String key, int borderWidth, int borderColor ) { + this.mContext = context; + this.mKey = key; + this.mBorderWidth = borderWidth; + this.mBorderColor = borderColor; + } + + @Override + protected Bitmap transform( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight ) { + int size = Math.min( toTransform.getWidth(), toTransform.getHeight() ); + int x = ( toTransform.getWidth() - size ) / 2 + mBorderWidth; + int y = ( toTransform.getHeight() - size ) / 2 + mBorderWidth; + int newSize = size - mBorderWidth * 2; + int radius = newSize / 2; + Bitmap bitmap = Bitmap.createBitmap( toTransform, x, y, newSize, newSize ); + Bitmap result = pool.get( newSize, newSize, toTransform.getConfig() ); + if ( result == null ) { + result = Bitmap.createBitmap( newSize, newSize, toTransform.getConfig() ); + } + + Canvas canvas = new Canvas( result ); + if ( mBorderWidth > 0 ) { + Paint borderPaint = new Paint(); + borderPaint.setStyle( Paint.Style.STROKE ); + borderPaint.setStrokeWidth( mBorderWidth ); + borderPaint.setColor( mBorderColor ); + borderPaint.setAntiAlias( true ); + canvas.drawCircle( radius, radius, radius - mBorderWidth / 2, borderPaint ); + } + Paint paint = new Paint(); + paint.setShader( new BitmapShader( bitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP ) ); + paint.setAntiAlias( true ); + canvas.drawCircle( radius, radius, radius - mBorderWidth, paint ); + + DiskLruCacheManager.getInstance( mContext ).put( mKey, result ); + + return result; + } + + @Override + public void updateDiskCacheKey( MessageDigest messageDigest ) { + + } +} \ No newline at end of file diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideRoundBitmapTransform.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideRoundBitmapTransform.java new file mode 100644 index 0000000000..261d7f9be4 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/transform/GlideRoundBitmapTransform.java @@ -0,0 +1,69 @@ + +package com.mogo.service.impl.imageloader.glide.transform; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; +import com.mogo.service.impl.imageloader.glide.utils.DiskLruCacheManager; + +import java.security.MessageDigest; + +public class GlideRoundBitmapTransform extends BitmapTransformation { + + private int mRadius; + private int mBorderWidth; + private int mBorderColor; + private String mKey; + private Context mContext; + + public GlideRoundBitmapTransform( Context context, String key, int radius, int borderWidth, int borderColor ) { + this.mContext = context; + this.mKey = key; + this.mRadius = radius; + this.mBorderWidth = borderWidth; + this.mBorderColor = borderColor; + } + + @Override + protected Bitmap transform( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight ) { + if ( mRadius == 0 && mBorderWidth == 0 ) { + return toTransform; + } + int width = toTransform.getWidth(); + int height = toTransform.getHeight(); + RectF rectF = new RectF( mBorderWidth, mBorderWidth, width - mBorderWidth, height - mBorderWidth ); + Bitmap result = pool.get( width, height, toTransform.getConfig() ); + if ( result == null ) { + result = toTransform.copy( toTransform.getConfig(), true ); + } + Canvas canvas = new Canvas( result ); + Paint paint = new Paint(); + paint.setShader( new BitmapShader( toTransform, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP ) ); + paint.setAntiAlias( true ); + canvas.drawRoundRect( rectF, mRadius, mRadius, paint ); + if ( mBorderWidth > 0 ) { + Paint borderPaint = new Paint(); + borderPaint.setStyle( Paint.Style.STROKE ); + borderPaint.setStrokeWidth( mBorderWidth ); + borderPaint.setColor( mBorderColor ); + borderPaint.setAntiAlias( true ); + canvas.drawRoundRect( rectF, mRadius, mRadius, borderPaint ); + } + DiskLruCacheManager.getInstance( mContext ).put( mKey, result ); + + return result; + } + + @Override + public void updateDiskCacheKey( MessageDigest messageDigest ) { + + } +} \ No newline at end of file diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCache.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCache.java new file mode 100644 index 0000000000..b0beddced2 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCache.java @@ -0,0 +1,974 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mogo.service.impl.imageloader.glide.utils; + +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A cache that uses a bounded amount of space on a filesystem. Each cache + * entry has a string key and a fixed number of values. Each key must match + * the regex [a-z0-9_-]{1,64}. Values are byte sequences, + * accessible as streams or files. Each value must be between {@code 0} and + * {@code Integer.MAX_VALUE} bytes in length. + * + *

The cache stores its data in a directory on the filesystem. This + * directory must be exclusive to the cache; the cache may delete or overwrite + * files from its directory. It is an error for multiple processes to use the + * same cache directory at the same time. + * + *

This cache limits the number of bytes that it will store on the + * filesystem. When the number of stored bytes exceeds the limit, the cache will + * remove entries in the background until the limit is satisfied. The limit is + * not strict: the cache may temporarily exceed it while waiting for files to be + * deleted. The limit does not include filesystem overhead or the cache + * journal so space-sensitive applications should set a conservative limit. + * + *

Clients call {@link #edit} to create or update the values of an entry. An + * entry may have only one editor at one time; if a value is not available to be + * edited then {@link #edit} will return null. + *

    + *
  • When an entry is being created it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + *
  • When an entry is being edited, it is not necessary + * to supply data for every value; values default to their previous + * value. + *
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit} + * or {@link Editor#abort}. Committing is atomic: a read observes the full set + * of values as they were before or after the commit, but never a mix of values. + * + *

Clients call {@link #get} to read a snapshot of an entry. The read will + * observe the value at the time that {@link #get} was called. Updates and + * removals after the call do not impact ongoing reads. + * + *

This class is tolerant of some I/O errors. If files are missing from the + * filesystem, the corresponding entries will be dropped from the cache. If + * an error occurs while writing a cache value, the edit will fail silently. + * Callers should handle other problems by catching {@code IOException} and + * responding appropriately. + */ +final class DiskLruCache implements Closeable { + static final String JOURNAL_FILE = "journal"; + static final String JOURNAL_FILE_TEMP = "journal.tmp"; + static final String JOURNAL_FILE_BACKUP = "journal.bkp"; + static final String MAGIC = "libcore.io.DiskLruCache"; + static final String VERSION_1 = "1"; + static final long ANY_SEQUENCE_NUMBER = -1; + static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}"); + private static final String CLEAN = "CLEAN"; + private static final String DIRTY = "DIRTY"; + private static final String REMOVE = "REMOVE"; + private static final String READ = "READ"; + + /* + * This cache uses a journal file named "journal". A typical journal file + * looks like this: + * libcore.io.DiskLruCache + * 1 + * 100 + * 2 + * + * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 + * DIRTY 335c4c6028171cfddfbaae1a9c313c52 + * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 + * REMOVE 335c4c6028171cfddfbaae1a9c313c52 + * DIRTY 1ab96a171faeeee38496d8b330771a7a + * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 + * READ 335c4c6028171cfddfbaae1a9c313c52 + * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 + * + * The first five lines of the journal form its header. They are the + * constant string "libcore.io.DiskLruCache", the disk cache's version, + * the application's version, the value count, and a blank line. + * + * Each of the subsequent lines in the file is a record of the state of a + * cache entry. Each line contains space-separated values: a state, a key, + * and optional state-specific values. + * o DIRTY lines track that an entry is actively being created or updated. + * Every successful DIRTY action should be followed by a CLEAN or REMOVE + * action. DIRTY lines without a matching CLEAN or REMOVE indicate that + * temporary files may need to be deleted. + * o CLEAN lines track a cache entry that has been successfully published + * and may be read. A publish line is followed by the lengths of each of + * its values. + * o READ lines track accesses for LRU. + * o REMOVE lines track entries that have been deleted. + * + * The journal file is appended to as cache operations occur. The journal may + * occasionally be compacted by dropping redundant lines. A temporary file named + * "journal.tmp" will be used during compaction; that file should be deleted if + * it exists when the cache is opened. + */ + + private final File directory; + private final File journalFile; + private final File journalFileTmp; + private final File journalFileBackup; + private final int appVersion; + private long maxSize; + private int maxFileCount; + private final int valueCount; + private long size = 0; + private int fileCount = 0; + private Writer journalWriter; + private final LinkedHashMap lruEntries = + new LinkedHashMap(0, 0.75f, true); + private int redundantOpCount; + + /** + * To differentiate between old and current snapshots, each entry is given + * a sequence number each time an edit is committed. A snapshot is stale if + * its sequence number is not equal to its entry's sequence number. + */ + private long nextSequenceNumber = 0; + + /** This cache uses a single background thread to evict entries. */ + final ThreadPoolExecutor executorService = + new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); + private final Callable cleanupCallable = new Callable() { + public Void call() throws Exception { + synchronized (DiskLruCache.this) { + if (journalWriter == null) { + return null; // Closed. + } + trimToSize(); + trimToFileCount(); + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } + return null; + } + }; + + private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) { + this.directory = directory; + this.appVersion = appVersion; + this.journalFile = new File(directory, JOURNAL_FILE); + this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); + this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); + this.valueCount = valueCount; + this.maxSize = maxSize; + this.maxFileCount = maxFileCount; + } + + /** + * Opens the cache in {@code directory}, creating a cache if none exists + * there. + * + * @param directory a writable directory + * @param valueCount the number of values per cache entry. Must be positive. + * @param maxSize the maximum number of bytes this cache should use to store + * @param maxFileCount the maximum file count this cache should store + * @throws IOException if reading or writing the cache directory fails + */ + public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) + throws IOException { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + if (maxFileCount <= 0) { + throw new IllegalArgumentException("maxFileCount <= 0"); + } + if (valueCount <= 0) { + throw new IllegalArgumentException("valueCount <= 0"); + } + + // If a bkp file exists, use it instead. + File backupFile = new File(directory, JOURNAL_FILE_BACKUP); + if (backupFile.exists()) { + File journalFile = new File(directory, JOURNAL_FILE); + // If journal file also exists just delete backup file. + if (journalFile.exists()) { + backupFile.delete(); + } else { + renameTo(backupFile, journalFile, false); + } + } + + // Prefer to pick up where we left off. + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount); + if (cache.journalFile.exists()) { + try { + cache.readJournal(); + cache.processJournal(); + cache.journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII)); + return cache; + } catch (IOException journalIsCorrupt) { + System.out + .println("DiskLruCache " + + directory + + " is corrupt: " + + journalIsCorrupt.getMessage() + + ", removing"); + cache.delete(); + } + } + + // Create a new empty cache. + directory.mkdirs(); + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount); + cache.rebuildJournal(); + return cache; + } + + private void readJournal() throws IOException { + StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); + try { + String magic = reader.readLine(); + String version = reader.readLine(); + String appVersionString = reader.readLine(); + String valueCountString = reader.readLine(); + String blank = reader.readLine(); + if (!MAGIC.equals(magic) + || !VERSION_1.equals(version) + || !Integer.toString(appVersion).equals(appVersionString) + || !Integer.toString(valueCount).equals(valueCountString) + || !"".equals(blank)) { + throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + + valueCountString + ", " + blank + "]"); + } + + int lineCount = 0; + while (true) { + try { + readJournalLine(reader.readLine()); + lineCount++; + } catch (EOFException endOfJournal) { + break; + } + } + redundantOpCount = lineCount - lruEntries.size(); + } finally { + Util.closeQuietly(reader); + } + } + + private void readJournalLine(String line) throws IOException { + int firstSpace = line.indexOf(' '); + if (firstSpace == -1) { + throw new IOException("unexpected journal line: " + line); + } + + int keyBegin = firstSpace + 1; + int secondSpace = line.indexOf(' ', keyBegin); + final String key; + if (secondSpace == -1) { + key = line.substring(keyBegin); + if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { + lruEntries.remove(key); + return; + } + } else { + key = line.substring(keyBegin, secondSpace); + } + + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } + + if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { + String[] parts = line.substring(secondSpace + 1).split(" "); + entry.readable = true; + entry.currentEditor = null; + entry.setLengths(parts); + } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { + entry.currentEditor = new Editor(entry); + } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { + // This work was already done by calling lruEntries.get(). + } else { + throw new IOException("unexpected journal line: " + line); + } + } + + /** + * Computes the initial size and collects garbage as a part of opening the + * cache. Dirty entries are assumed to be inconsistent and will be deleted. + */ + private void processJournal() throws IOException { + deleteIfExists(journalFileTmp); + for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { + Entry entry = i.next(); + if (entry.currentEditor == null) { + for (int t = 0; t < valueCount; t++) { + size += entry.lengths[t]; + fileCount++; + } + } else { + entry.currentEditor = null; + for (int t = 0; t < valueCount; t++) { + deleteIfExists(entry.getCleanFile(t)); + deleteIfExists(entry.getDirtyFile(t)); + } + i.remove(); + } + } + } + + /** + * Creates a new journal that omits redundant information. This replaces the + * current journal if it exists. + */ + private synchronized void rebuildJournal() throws IOException { + if (journalWriter != null) { + journalWriter.close(); + } + + Writer writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII)); + try { + writer.write(MAGIC); + writer.write("\n"); + writer.write(VERSION_1); + writer.write("\n"); + writer.write(Integer.toString(appVersion)); + writer.write("\n"); + writer.write(Integer.toString(valueCount)); + writer.write("\n"); + writer.write("\n"); + + for (Entry entry : lruEntries.values()) { + if (entry.currentEditor != null) { + writer.write(DIRTY + ' ' + entry.key + '\n'); + } else { + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } + } + } finally { + writer.close(); + } + + if (journalFile.exists()) { + renameTo(journalFile, journalFileBackup, true); + } + renameTo(journalFileTmp, journalFile, false); + journalFileBackup.delete(); + + journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); + } + + private static void deleteIfExists(File file) throws IOException { + if (file.exists() && !file.delete()) { + throw new IOException(); + } + } + + private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { + if (deleteDestination) { + deleteIfExists(to); + } + if (!from.renameTo(to)) { + throw new IOException(); + } + } + + /** + * Returns a snapshot of the entry named {@code key}, or null if it doesn't + * exist is not currently readable. If a value is returned, it is moved to + * the head of the LRU queue. + */ + public synchronized Snapshot get(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null) { + return null; + } + + if (!entry.readable) { + return null; + } + + // Open all streams eagerly to guarantee that we see a single published + // snapshot. If we opened streams lazily then the streams could come + // from different edits. + File[] files = new File[valueCount]; + InputStream[] ins = new InputStream[valueCount]; + try { + File file; + for (int i = 0; i < valueCount; i++) { + file = entry.getCleanFile(i); + files[i] = file; + ins[i] = new FileInputStream(file); + } + } catch (FileNotFoundException e) { + // A file must have been deleted manually! + for (int i = 0; i < valueCount; i++) { + if (ins[i] != null) { + Util.closeQuietly(ins[i]); + } else { + break; + } + } + return null; + } + + redundantOpCount++; + journalWriter.append(READ + ' ' + key + '\n'); + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths); + } + + /** + * Returns an editor for the entry named {@code key}, or null if another + * edit is in progress. + */ + public Editor edit(String key) throws IOException { + return edit(key, ANY_SEQUENCE_NUMBER); + } + + private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null + || entry.sequenceNumber != expectedSequenceNumber)) { + return null; // Snapshot is stale. + } + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } else if (entry.currentEditor != null) { + return null; // Another edit is in progress. + } + + Editor editor = new Editor(entry); + entry.currentEditor = editor; + + // Flush the journal before creating files to prevent file leaks. + journalWriter.write(DIRTY + ' ' + key + '\n'); + journalWriter.flush(); + return editor; + } + + /** Returns the directory where this cache stores its data. */ + public File getDirectory() { + return directory; + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public synchronized long getMaxSize() { + return maxSize; + } + + /** Returns the maximum number of files that this cache should store */ + public synchronized int getMaxFileCount() { + return maxFileCount; + } + + /** + * Changes the maximum number of bytes the cache can store and queues a job + * to trim the existing store, if necessary. + */ + public synchronized void setMaxSize(long maxSize) { + this.maxSize = maxSize; + executorService.submit(cleanupCallable); + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the max size if a background + * deletion is pending. + */ + public synchronized long size() { + return size; + } + + /** + * Returns the number of files currently being used to store the values in + * this cache. This may be greater than the max file count if a background + * deletion is pending. + */ + public synchronized long fileCount() { + return fileCount; + } + + private synchronized void completeEdit(Editor editor, boolean success) throws IOException { + Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // If this edit is creating the entry for the first time, every index must have a value. + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!editor.written[i]) { + editor.abort(); + throw new IllegalStateException("Newly created entry didn't create value for index " + i); + } + if (!entry.getDirtyFile(i).exists()) { + editor.abort(); + return; + } + } + } + + for (int i = 0; i < valueCount; i++) { + File dirty = entry.getDirtyFile(i); + if (success) { + if (dirty.exists()) { + File clean = entry.getCleanFile(i); + dirty.renameTo(clean); + long oldLength = entry.lengths[i]; + long newLength = clean.length(); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + fileCount++; + } + } else { + deleteIfExists(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + if (success) { + entry.sequenceNumber = nextSequenceNumber++; + } + } else { + lruEntries.remove(entry.key); + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); + } + journalWriter.flush(); + + if (size > maxSize || fileCount > maxFileCount || journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + } + + /** + * We only rebuild the journal when it will halve the size of the journal + * and eliminate at least 2000 ops. + */ + private boolean journalRebuildRequired() { + final int redundantOpCompactThreshold = 2000; + return redundantOpCount >= redundantOpCompactThreshold // + && redundantOpCount >= lruEntries.size(); + } + + /** + * Drops the entry for {@code key} if it exists and can be removed. Entries + * actively being edited cannot be removed. + * + * @return true if an entry was removed. + */ + public synchronized boolean remove(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null || entry.currentEditor != null) { + return false; + } + + for (int i = 0; i < valueCount; i++) { + File file = entry.getCleanFile(i); + if (file.exists() && !file.delete()) { + throw new IOException("failed to delete " + file); + } + size -= entry.lengths[i]; + fileCount--; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.append(REMOVE + ' ' + key + '\n'); + lruEntries.remove(key); + + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return true; + } + + /** Returns true if this cache has been closed. */ + public synchronized boolean isClosed() { + return journalWriter == null; + } + + private void checkNotClosed() { + if (journalWriter == null) { + throw new IllegalStateException("cache is closed"); + } + } + + /** Force buffered operations to the filesystem. */ + public synchronized void flush() throws IOException { + checkNotClosed(); + trimToSize(); + trimToFileCount(); + journalWriter.flush(); + } + + /** Closes this cache. Stored values will remain on the filesystem. */ + public synchronized void close() throws IOException { + if (journalWriter == null) { + return; // Already closed. + } + for (Entry entry : new ArrayList(lruEntries.values())) { + if (entry.currentEditor != null) { + entry.currentEditor.abort(); + } + } + trimToSize(); + trimToFileCount(); + journalWriter.close(); + journalWriter = null; + } + + private void trimToSize() throws IOException { + while (size > maxSize) { + Map.Entry toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); + } + } + + private void trimToFileCount() throws IOException { + while (fileCount > maxFileCount) { + Map.Entry toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); + } + } + + /** + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. + */ + public void delete() throws IOException { + close(); + Util.deleteContents(directory); + } + + private void validateKey(String key) { + Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\""); + } + } + + private static String inputStreamToString(InputStream in) throws IOException { + return Util.readFully(new InputStreamReader(in, Util.UTF_8)); + } + + /** A snapshot of the values for an entry. */ + public final class Snapshot implements Closeable { + private final String key; + private final long sequenceNumber; + private File[] files; + private final InputStream[] ins; + private final long[] lengths; + + private Snapshot(String key, long sequenceNumber, File[] files, InputStream[] ins, long[] lengths) { + this.key = key; + this.sequenceNumber = sequenceNumber; + this.files = files; + this.ins = ins; + this.lengths = lengths; + } + + /** + * Returns an editor for this snapshot's entry, or null if either the + * entry has changed since this snapshot was created or if another edit + * is in progress. + */ + public Editor edit() throws IOException { + return DiskLruCache.this.edit(key, sequenceNumber); + } + + /** Returns file with the value for {@code index}. */ + public File getFile(int index) { + return files[index]; + } + + /** Returns the unbuffered stream with the value for {@code index}. */ + public InputStream getInputStream(int index) { + return ins[index]; + } + + /** Returns the string value for {@code index}. */ + public String getString(int index) throws IOException { + return inputStreamToString(getInputStream(index)); + } + + /** Returns the byte length of the value for {@code index}. */ + public long getLength(int index) { + return lengths[index]; + } + + public void close() { + for (InputStream in : ins) { + Util.closeQuietly(in); + } + } + } + + private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { + @Override + public void write(int b) throws IOException { + // Eat all writes silently. Nom nom. + } + }; + + /** Edits the values for an entry. */ + public final class Editor { + private final Entry entry; + private final boolean[] written; + private boolean hasErrors; + private boolean committed; + + private Editor(Entry entry) { + this.entry = entry; + this.written = (entry.readable) ? null : new boolean[valueCount]; + } + + /** + * Returns an unbuffered input stream to read the last committed value, + * or null if no value has been committed. + */ + public InputStream newInputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + return null; + } + try { + return new FileInputStream(entry.getCleanFile(index)); + } catch (FileNotFoundException e) { + return null; + } + } + } + + /** + * Returns the last committed value as a string, or null if no value + * has been committed. + */ + public String getString(int index) throws IOException { + InputStream in = newInputStream(index); + return in != null ? inputStreamToString(in) : null; + } + + /** + * Returns a new unbuffered output stream to write the value at + * {@code index}. If the underlying output stream encounters errors + * when writing to the filesystem, this edit will be aborted when + * {@link #commit} is called. The returned output stream does not throw + * IOExceptions. + */ + public OutputStream newOutputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + written[index] = true; + } + File dirtyFile = entry.getDirtyFile(index); + FileOutputStream outputStream; + try { + outputStream = new FileOutputStream(dirtyFile); + } catch (FileNotFoundException e) { + // Attempt to recreate the cache directory. + directory.mkdirs(); + try { + outputStream = new FileOutputStream(dirtyFile); + } catch (FileNotFoundException e2) { + // We are unable to recover. Silently eat the writes. + return NULL_OUTPUT_STREAM; + } + } + return new FaultHidingOutputStream(outputStream); + } + } + + /** Sets the value at {@code index} to {@code value}. */ + public void set(int index, String value) throws IOException { + Writer writer = null; + try { + writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); + writer.write(value); + } finally { + Util.closeQuietly(writer); + } + } + + /** + * Commits this edit so it is visible to readers. This releases the + * edit lock so another edit may be started on the same key. + */ + public void commit() throws IOException { + if (hasErrors) { + completeEdit(this, false); + remove(entry.key); // The previous entry is stale. + } else { + completeEdit(this, true); + } + committed = true; + } + + /** + * Aborts this edit. This releases the edit lock so another edit may be + * started on the same key. + */ + public void abort() throws IOException { + completeEdit(this, false); + } + + public void abortUnlessCommitted() { + if (!committed) { + try { + abort(); + } catch (IOException ignored) { + } + } + } + + private class FaultHidingOutputStream extends FilterOutputStream { + private FaultHidingOutputStream(OutputStream out) { + super(out); + } + + @Override public void write(int oneByte) { + try { + out.write(oneByte); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void write(byte[] buffer, int offset, int length) { + try { + out.write(buffer, offset, length); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void close() { + try { + out.close(); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void flush() { + try { + out.flush(); + } catch (IOException e) { + hasErrors = true; + } + } + } + } + + private final class Entry { + private final String key; + + /** Lengths of this entry's files. */ + private final long[] lengths; + + /** True if this entry has ever been published. */ + private boolean readable; + + /** The ongoing edit or null if this entry is not being edited. */ + private Editor currentEditor; + + /** The sequence number of the most recently committed edit to this entry. */ + private long sequenceNumber; + + private Entry(String key) { + this.key = key; + this.lengths = new long[valueCount]; + } + + public String getLengths() throws IOException { + StringBuilder result = new StringBuilder(); + for (long size : lengths) { + result.append(' ').append(size); + } + return result.toString(); + } + + /** Set lengths using decimal numbers like "10123". */ + private void setLengths(String[] strings) throws IOException { + if (strings.length != valueCount) { + throw invalidLengths(strings); + } + + try { + for (int i = 0; i < strings.length; i++) { + lengths[i] = Long.parseLong(strings[i]); + } + } catch (NumberFormatException e) { + throw invalidLengths(strings); + } + } + + private IOException invalidLengths(String[] strings) throws IOException { + throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); + } + + public File getCleanFile(int i) { + return new File(directory, key + "" + i); + } + + public File getDirtyFile(int i) { + return new File(directory, key + "" + i + ".tmp"); + } + } +} diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCacheManager.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCacheManager.java new file mode 100644 index 0000000000..0c38ed7c12 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/DiskLruCacheManager.java @@ -0,0 +1,101 @@ +package com.mogo.service.impl.imageloader.glide.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.TextUtils; + +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.MessageDigest; + +/******************************************************************************* + * Description: 用于缓存经过高斯模糊的图片 + * + * Author: Freeman + * + * Date: 2018/9/4 + * + * Copyright: all rights reserved by Freeman. + *******************************************************************************/ +public class DiskLruCacheManager { + + private DiskLruCache diskLruCache; + private static DiskLruCacheManager instance; + + private final int MAX_CACHE_SIZE = 64 * 1024 * 1024; + + private DiskLruCacheManager(Context context) { + try { + diskLruCache = DiskLruCache.open(context.getCacheDir(), 1, 1, + MAX_CACHE_SIZE, Integer.MAX_VALUE); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } + + public static DiskLruCacheManager getInstance(Context context) { + if (instance == null) { + synchronized (DiskLruCacheManager.class) { + if (instance == null) { + instance = new DiskLruCacheManager(context.getApplicationContext()); + } + } + } + + return instance; + } + + public void put(String url, Bitmap bitmap) { + if (TextUtils.isEmpty(url) || bitmap == null || bitmap.isRecycled()) { + return; + } + + try { + DiskLruCache.Editor editor = diskLruCache.edit(getKey(url)); + OutputStream outputStream = editor.newOutputStream(0); + if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) { + editor.commit(); + } + diskLruCache.flush(); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } + + public Bitmap get(String url) { + try { + DiskLruCache.Snapshot snapshot = diskLruCache.get(getKey(url)); + if (snapshot != null) { + InputStream inputStream = snapshot.getInputStream(0); + return BitmapFactory.decodeStream(inputStream); + } + } catch (Exception e) { + e.printStackTrace(System.err); + } + + return null; + } + + public static String getKey(String url) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] md5 = digest.digest(url.getBytes()); + BigInteger bigInteger = new BigInteger(1, md5); + return bigInteger.toString(16); + } catch (Exception e) { + e.printStackTrace(System.err); + } + + return null; + } + + public void close() { + try { + diskLruCache.close(); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } +} diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/FastBlurUtil.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/FastBlurUtil.java new file mode 100755 index 0000000000..9de44f0cd3 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/FastBlurUtil.java @@ -0,0 +1,241 @@ +package com.mogo.service.impl.imageloader.glide.utils; + +import android.graphics.Bitmap; + +/** + * Created by jay on 11/7/15. + */ +public class FastBlurUtil { + + public static Bitmap doBlur(Bitmap sentBitmap, int scaleRadius, int radius) { + + // Stack Blur v1.0 from + // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html + // + // Java Author: Mario Klingemann + // http://incubator.quasimondo.com + // created Feburary 29, 2004 + // Android port : Yahel Bouaziz + // http://www.kayenko.com + // ported april 5th, 2012 + + // This is a compromise between Gaussian Blur and Box blur + // It creates much better looking blurs than Box Blur, but is + // 7x faster than my Gaussian Blur implementation. + // + // I called it Stack Blur because this describes best how this + // filter works internally: it creates a kind of moving stack + // of colors whilst scanning through the image. Thereby it + // just has to add one new block of color to the right side + // of the stack and remove the leftmost color. The remaining + // colors on the topmost layer of the stack are either added on + // or reduced by one, depending on if they are on the right or + // on the left side of the stack. + // + // If you are using this algorithm in your code please add + // the following line: + // + // Stack Blur Algorithm by Mario Klingemann + if (scaleRadius > 0) { + sentBitmap = Bitmap.createScaledBitmap(sentBitmap, sentBitmap.getWidth() / scaleRadius, + sentBitmap.getHeight() / scaleRadius, false); + } + Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); + + if (radius < 1) { + return (null); + } + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + int[] pix = new int[w * h]; + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int r[] = new int[wh]; + int g[] = new int[wh]; + int b[] = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int vmin[] = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int dv[] = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } + + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < h; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < w; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += w; + } + for (x = 0; x < w; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += w; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += w; + } + } + + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + + return (bitmap); + } +} diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/StrictLineReader.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/StrictLineReader.java new file mode 100644 index 0000000000..6083bb107a --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/StrictLineReader.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mogo.service.impl.imageloader.glide.utils; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +/** + * Buffers input from an {@link InputStream} for reading lines. + * + *

This class is used for buffered reading of lines. For purposes of this class, a line ends + * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated + * line at end of input is invalid and will be ignored, the caller may use {@code + * hasUnterminatedLine()} to detect it after catching the {@code EOFException}. + * + *

This class is intended for reading input that strictly consists of lines, such as line-based + * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction + * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different + * end-of-input reporting and a more restrictive definition of a line. + * + *

This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 + * and 10, respectively, and the representation of no other character contains these values. + * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. + * The default charset is US_ASCII. + */ +class StrictLineReader implements Closeable { + private static final byte CR = (byte) '\r'; + private static final byte LF = (byte) '\n'; + + private final InputStream in; + private final Charset charset; + + /* + * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end + * and the data in the range [pos, end) is buffered for reading. At end of input, if there is + * an unterminated line, we set end == -1, otherwise end == pos. If the underlying + * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. + */ + private byte[] buf; + private int pos; + private int end; + + /** + * Constructs a new {@code LineReader} with the specified charset and the default capacity. + * + * @param in the {@code InputStream} to read data from. + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are + * supported. + * @throws NullPointerException if {@code in} or {@code charset} is null. + * @throws IllegalArgumentException if the specified charset is not supported. + */ + public StrictLineReader(InputStream in, Charset charset) { + this(in, 8192, charset); + } + + /** + * Constructs a new {@code LineReader} with the specified capacity and charset. + * + * @param in the {@code InputStream} to read data from. + * @param capacity the capacity of the buffer. + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are + * supported. + * @throws NullPointerException if {@code in} or {@code charset} is null. + * @throws IllegalArgumentException if {@code capacity} is negative or zero + * or the specified charset is not supported. + */ + public StrictLineReader(InputStream in, int capacity, Charset charset) { + if (in == null || charset == null) { + throw new NullPointerException(); + } + if (capacity < 0) { + throw new IllegalArgumentException("capacity <= 0"); + } + if (!(charset.equals(Util.US_ASCII))) { + throw new IllegalArgumentException("Unsupported encoding"); + } + + this.in = in; + this.charset = charset; + buf = new byte[capacity]; + } + + /** + * Closes the reader by closing the underlying {@code InputStream} and + * marking this reader as closed. + * + * @throws IOException for errors when closing the underlying {@code InputStream}. + */ + public void close() throws IOException { + synchronized (in) { + if (buf != null) { + buf = null; + in.close(); + } + } + } + + /** + * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, + * this end of line marker is not included in the result. + * + * @return the next line from the input. + * @throws IOException for underlying {@code InputStream} errors. + * @throws EOFException for the end of source stream. + */ + public String readLine() throws IOException { + synchronized (in) { + if (buf == null) { + throw new IOException("LineReader is closed"); + } + + // Read more data if we are at the end of the buffered data. + // Though it's an error to read after an exception, we will let {@code fillBuf()} + // throw again if that happens; thus we need to handle end == -1 as well as end == pos. + if (pos >= end) { + fillBuf(); + } + // Try to find LF in the buffered data and return the line if successful. + for (int i = pos; i != end; ++i) { + if (buf[i] == LF) { + int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; + String res = new String(buf, pos, lineEnd - pos, charset.name()); + pos = i + 1; + return res; + } + } + + // Let's anticipate up to 80 characters on top of those already read. + ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { + @Override + public String toString() { + int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; + try { + return new String(buf, 0, length, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); // Since we control the charset this will never happen. + } + } + }; + + while (true) { + out.write(buf, pos, end - pos); + // Mark unterminated line in case fillBuf throws EOFException or IOException. + end = -1; + fillBuf(); + // Try to find LF in the buffered data and return the line if successful. + for (int i = pos; i != end; ++i) { + if (buf[i] == LF) { + if (i != pos) { + out.write(buf, pos, i - pos); + } + pos = i + 1; + return out.toString(); + } + } + } + } + } + + /** + * Reads new input data into the buffer. Call only with pos == end or end == -1, + * depending on the desired outcome if the function throws. + */ + private void fillBuf() throws IOException { + int result = in.read(buf, 0, buf.length); + if (result == -1) { + throw new EOFException(); + } + pos = 0; + end = result; + } +} + diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/Util.java b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/Util.java new file mode 100644 index 0000000000..eaf34b7a49 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/imageloader/glide/utils/Util.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mogo.service.impl.imageloader.glide.utils; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.nio.charset.Charset; + +/** Junk drawer of utility methods. */ +final class Util { + static final Charset US_ASCII = Charset.forName("US-ASCII"); + static final Charset UTF_8 = Charset.forName("UTF-8"); + + private Util() { + } + + static String readFully(Reader reader) throws IOException { + try { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + return writer.toString(); + } finally { + reader.close(); + } + } + + /** + * Deletes the contents of {@code dir}. Throws an IOException if any file + * could not be deleted, or if {@code dir} is not a readable directory. + */ + static void deleteContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("not a readable directory: " + dir); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } + } + + static void closeQuietly(/*Auto*/Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } +} \ No newline at end of file diff --git a/services/mogo-service/src/main/java/com/mogo/service/impl/map/MogoMapService.java b/services/mogo-service/src/main/java/com/mogo/service/impl/map/MogoMapService.java new file mode 100644 index 0000000000..e1ab65de28 --- /dev/null +++ b/services/mogo-service/src/main/java/com/mogo/service/impl/map/MogoMapService.java @@ -0,0 +1,68 @@ +package com.mogo.service.impl.map; + +import android.content.Context; + +import com.alibaba.android.arouter.facade.annotation.Route; +import com.mogo.map.MogoMap; +import com.mogo.map.location.IMogoLocationClient; +import com.mogo.map.location.MogoLocationClient; +import com.mogo.map.marker.IMogoMarker; +import com.mogo.map.marker.MogoMarkerOptions; +import com.mogo.map.search.IMogoGeoSearch; +import com.mogo.map.search.IMogoInputtipsSearch; +import com.mogo.map.search.MogoGeoSearch; +import com.mogo.map.search.MogoInputtipsSearch; +import com.mogo.map.search.query.MogoInputtipsQuery; +import com.mogo.service.map.IMogoMapService; +import com.mogo.service.MogoServicePaths; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author congtaowang + * @since 2019-12-18 + *

+ * 地图对外地接口 + */ +@Route( path = MogoServicePaths.PATH_MAP_SERVICE ) +public class MogoMapService implements IMogoMapService { + + @Override + public IMogoLocationClient getSingletonLocationClient( Context context ) { + return MogoLocationClient.getInstance( context ); + } + + @Override + public IMogoMarker addMarker( MogoMarkerOptions options ) { + try { + return MogoMap.getInstance().getMogoMap().addMarker( options ); + } catch ( Exception e ) { + return null; + } + } + + @Override + public List< IMogoMarker > addMarkers( ArrayList< MogoMarkerOptions > options, boolean moveToCenter ) { + try { + return MogoMap.getInstance().getMogoMap().addMarkers( options, moveToCenter ); + } catch ( Exception e ) { + return null; + } + } + + @Override + public IMogoInputtipsSearch getInputtipsSearch( Context context, MogoInputtipsQuery query ) { + return new MogoInputtipsSearch( context, query ); + } + + @Override + public IMogoGeoSearch getGeoSearch( Context context ) { + return new MogoGeoSearch( context ); + } + + @Override + public void init( Context context ) { + + } +} diff --git a/services/mogo-service/src/main/res/values/strings.xml b/services/mogo-service/src/main/res/values/strings.xml new file mode 100644 index 0000000000..eb1356c817 --- /dev/null +++ b/services/mogo-service/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + amap-api + diff --git a/services/mogo-service/src/test/java/com/mogo/service/map/ExampleUnitTest.java b/services/mogo-service/src/test/java/com/mogo/service/map/ExampleUnitTest.java new file mode 100644 index 0000000000..6c7fa71433 --- /dev/null +++ b/services/mogo-service/src/test/java/com/mogo/service/map/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.mogo.service.map; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals( 4, 2 + 2 ); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..e1990b3af7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,7 @@ +rootProject.name = 'MogoLauncher' +include ':app' +include ':foudations:mogo-utils' +include ':services:mogo-service-api' +include ':services:mogo-service' +include ':libraries:mogo-map' +include ':foudations:mogo-commons'