From c57388dc949fdcf07fd2ff6291f7a6e6e0c95e31 Mon Sep 17 00:00:00 2001 From: ScorpioMiku <1056992492@qq.com> Date: Tue, 28 Aug 2018 13:31:46 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A1=E6=AD=A5ok=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 3 +- app/src/main/AndroidManifest.xml | 7 +- .../nutritionmaster/NutritionMaster.java | 50 +- .../nutritionmaster/modules/MainActivity.java | 21 +- .../BodyInformationFragment.java | 110 ++++- .../nutritionmaster/step/StepStarter.java | 28 ++ .../res/layout/body_information_fragment.xml | 8 +- settings.gradle | 2 +- todaystepcounterlib/.gitignore | 1 + todaystepcounterlib/build.gradle | 34 ++ .../libs/microlog4android-1.0.0.jar | Bin 0 -> 74651 bytes todaystepcounterlib/proguard-rules.pro | 25 + .../src/main/AndroidManifest.xml | 70 +++ .../today/step/lib/ISportStepInterface.aidl | 35 ++ .../src/main/assets/microlog.properties | 4 + .../today/step/lib/BaseClickBroadcast.java | 12 + .../java/com/today/step/lib/DateUtils.java | 72 +++ .../today/step/lib/ITodayStepDBHelper.java | 29 ++ .../today/step/lib/JobSchedulerService.java | 35 ++ .../main/java/com/today/step/lib/Logger.java | 66 +++ .../com/today/step/lib/Microlog4Android.java | 35 ++ .../today/step/lib/OnStepCounterListener.java | 20 + .../com/today/step/lib/PreferencesHelper.java | 119 +++++ .../today/step/lib/SportStepJsonUtils.java | 57 +++ .../today/step/lib/StepAlertManagerUtils.java | 50 ++ .../today/step/lib/TodayStepAlertReceive.java | 30 ++ .../lib/TodayStepBootCompleteReceiver.java | 25 + .../com/today/step/lib/TodayStepCounter.java | 226 +++++++++ .../com/today/step/lib/TodayStepDBHelper.java | 201 ++++++++ .../com/today/step/lib/TodayStepData.java | 84 ++++ .../com/today/step/lib/TodayStepDetector.java | 309 ++++++++++++ .../com/today/step/lib/TodayStepManager.java | 63 +++ .../com/today/step/lib/TodayStepService.java | 467 ++++++++++++++++++ .../step/lib/TodayStepShutdownReceiver.java | 23 + .../com/today/step/lib/WakeLockUtils.java | 39 ++ .../ic_notification_default.png | Bin 0 -> 10486 bytes .../src/main/res/values/strings.xml | 4 + .../MyRobolectricTestRunner.java | 53 ++ .../TodayStepDBHelperTest.java | 110 +++++ 39 files changed, 2495 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/example/ninefourone/nutritionmaster/step/StepStarter.java create mode 100644 todaystepcounterlib/.gitignore create mode 100644 todaystepcounterlib/build.gradle create mode 100644 todaystepcounterlib/libs/microlog4android-1.0.0.jar create mode 100644 todaystepcounterlib/proguard-rules.pro create mode 100644 todaystepcounterlib/src/main/AndroidManifest.xml create mode 100644 todaystepcounterlib/src/main/aidl/com/today/step/lib/ISportStepInterface.aidl create mode 100644 todaystepcounterlib/src/main/assets/microlog.properties create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/BaseClickBroadcast.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/DateUtils.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/ITodayStepDBHelper.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/JobSchedulerService.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/Logger.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/Microlog4Android.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/OnStepCounterListener.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/PreferencesHelper.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/SportStepJsonUtils.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/StepAlertManagerUtils.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepAlertReceive.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepBootCompleteReceiver.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepCounter.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDBHelper.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepData.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDetector.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepManager.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepService.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepShutdownReceiver.java create mode 100644 todaystepcounterlib/src/main/java/com/today/step/lib/WakeLockUtils.java create mode 100644 todaystepcounterlib/src/main/res/mipmap-xxxhdpi/ic_notification_default.png create mode 100644 todaystepcounterlib/src/main/res/values/strings.xml create mode 100644 todaystepcounterlib/src/test/java/com.today.step.lib/MyRobolectricTestRunner.java create mode 100644 todaystepcounterlib/src/test/java/com.today.step.lib/TodayStepDBHelperTest.java diff --git a/app/build.gradle b/app/build.gradle index 938470f..589fa30 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,7 +24,7 @@ android { } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support:design:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.1.0' @@ -51,4 +51,5 @@ dependencies { compile 'com.akexorcist:RoundCornerProgressBar:2.0.3' //wave compile 'com.gelitenight.waveview:waveview:1.0.0' + implementation project(':todaystepcounterlib') } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 978e5b9..1f20f88 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,7 +19,7 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/NutritionMaster.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/NutritionMaster.java index ec33f70..ff65488 100644 --- a/app/src/main/java/com/example/ninefourone/nutritionmaster/NutritionMaster.java +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/NutritionMaster.java @@ -1,6 +1,8 @@ package com.example.ninefourone.nutritionmaster; +import android.app.Activity; import android.app.Application; +import android.os.Bundle; import com.orhanobut.logger.AndroidLogAdapter; import com.orhanobut.logger.Logger; @@ -12,6 +14,7 @@ import com.orhanobut.logger.Logger; public class NutritionMaster extends Application { public static NutritionMaster mInstance; + private int appCount = 0; @Override public void onCreate() { @@ -25,10 +28,55 @@ public class NutritionMaster extends Application { */ private void init() { Logger.addLogAdapter(new AndroidLogAdapter()); - Logger.d("Logger初始化成功"); + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + + } + + @Override + public void onActivityStarted(Activity activity) { + appCount++; + } + + @Override + public void onActivityResumed(Activity activity) { + + } + + @Override + public void onActivityPaused(Activity activity) { + + } + + @Override + public void onActivityStopped(Activity activity) { + appCount--; + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + + } + + @Override + public void onActivityDestroyed(Activity activity) { + + } + }); } public static NutritionMaster getInstance() { return mInstance; } + + /** + * app是否在前台 + * + * @return true前台,false后台 + */ + public boolean isForeground() { + return appCount > 0; + } + } diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/MainActivity.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/MainActivity.java index 8c08218..bb17b74 100644 --- a/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/MainActivity.java +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/MainActivity.java @@ -1,19 +1,16 @@ package com.example.ninefourone.nutritionmaster.modules; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; import android.widget.LinearLayout; -import com.ToxicBakery.viewpager.transforms.AccordionTransformer; -import com.ToxicBakery.viewpager.transforms.CubeInTransformer; import com.ToxicBakery.viewpager.transforms.CubeOutTransformer; -import com.ToxicBakery.viewpager.transforms.DepthPageTransformer; -import com.ToxicBakery.viewpager.transforms.FlipHorizontalTransformer; -import com.ToxicBakery.viewpager.transforms.FlipVerticalTransformer; -import com.ToxicBakery.viewpager.transforms.RotateUpTransformer; -import com.ToxicBakery.viewpager.transforms.StackTransformer; -import com.ToxicBakery.viewpager.transforms.TabletTransformer; -import com.ToxicBakery.viewpager.transforms.ZoomInTransformer; import com.example.ninefourone.nutritionmaster.R; import com.example.ninefourone.nutritionmaster.adapter.HomePagerAdapter; import com.example.ninefourone.nutritionmaster.base.BaseActivity; @@ -21,6 +18,9 @@ import com.example.ninefourone.nutritionmaster.ui.NoScrollViewPager; import com.flyco.tablayout.SlidingTabLayout; import com.mxn.soul.flowingdrawer_core.ElasticDrawer; import com.mxn.soul.flowingdrawer_core.FlowingDrawer; +import com.today.step.lib.ISportStepInterface; +import com.today.step.lib.TodayStepManager; +import com.today.step.lib.TodayStepService; import butterknife.BindView; import butterknife.ButterKnife; @@ -75,7 +75,6 @@ public class MainActivity extends BaseActivity { viewPager.setPageTransformer(true, new CubeOutTransformer()); slidingTabLayout.setViewPager(viewPager); viewPager.setCurrentItem(1); - } @Override @@ -102,4 +101,6 @@ public class MainActivity extends BaseActivity { public void onViewClicked() { mDrawer.openMenu(); } + + } diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/bodyinformation/BodyInformationFragment.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/bodyinformation/BodyInformationFragment.java index c36c892..9befe2a 100644 --- a/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/bodyinformation/BodyInformationFragment.java +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/bodyinformation/BodyInformationFragment.java @@ -1,16 +1,28 @@ package com.example.ninefourone.nutritionmaster.modules.viewpagerfragments.bodyinformation; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.graphics.Color; import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import com.akexorcist.roundcornerprogressbar.IconRoundCornerProgressBar; import com.akexorcist.roundcornerprogressbar.RoundCornerProgressBar; import com.example.ninefourone.nutritionmaster.R; import com.example.ninefourone.nutritionmaster.base.BaseFragment; -import com.gelitenight.waveview.library.WaveView; +import com.today.step.lib.ISportStepInterface; +import com.today.step.lib.TodayStepManager; +import com.today.step.lib.TodayStepService; import butterknife.BindView; import butterknife.ButterKnife; @@ -27,8 +39,19 @@ public class BodyInformationFragment extends BaseFragment { @BindView(R.id.progress_2) IconRoundCornerProgressBar progress2; Unbinder unbinder; - @BindView(R.id.wave_view) - WaveView waveView; + @BindView(R.id.step_text_view) + TextView stepTextView; + + private int stepCount = 0; + private static final int REFRESH_STEP_WHAT = 0; + + //循环取当前时刻的步数中间的间隔时间 + private long TIME_INTERVAL_REFRESH = 1000; + + private Handler mDelayHandler = new Handler(new TodayStepCounterCall()); + + private ISportStepInterface iSportStepInterface; + @Override public int getLayoutResId() { @@ -42,12 +65,6 @@ public class BodyInformationFragment extends BaseFragment { progress1.setMax(70); progress1.setProgress(15); - int progressColor1 = progress1.getProgressColor(); - int backgroundColor1 = progress1.getProgressBackgroundColor(); - int max1 = (int) progress1.getMax(); - int progress_1 = (int) progress1.getProgress(); - - progress2.setProgressColor(Color.parseColor("#56d2c2")); progress2.setProgressBackgroundColor(Color.parseColor("#757575")); progress2.setIconBackgroundColor(Color.parseColor("#38c0ae")); @@ -55,13 +72,9 @@ public class BodyInformationFragment extends BaseFragment { progress2.setProgress(147); progress2.setIconImageResource(R.drawable.test_avatar); - int progressColor2 = progress2.getProgressColor(); - int backgroundColor2 = progress2.getProgressBackgroundColor(); - int headerColor2 = progress2.getColorIconBackground(); - int max2 = (int) progress2.getMax(); - int progress_2 = (int) progress2.getProgress(); - waveView.setShapeType(WaveView.ShapeType.CIRCLE); + initStepCounter(); +// Logger.d(((MainActivity)getActivity()).getStepCount()); } @@ -82,4 +95,71 @@ public class BodyInformationFragment extends BaseFragment { super.onDestroyView(); unbinder.unbind(); } + + /** + * 计步器初始化 + */ + private void initStepCounter() { + TodayStepManager.init(getActivity().getApplication()); + //开启计步 + Intent stepCounterStart = new Intent(getActivity(), TodayStepService.class); + getActivity().startService(stepCounterStart); + getActivity().bindService(stepCounterStart, new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + iSportStepInterface = ISportStepInterface.Stub.asInterface(service); + try { + stepCount = iSportStepInterface.getCurrentTimeSportStep(); + updateStepCount(); + } catch (RemoteException e) { + e.printStackTrace(); + } + mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + + } + }, Context.BIND_AUTO_CREATE); + } + + /** + * 改变记步UI中的数字 + */ + private void updateStepCount() { + stepTextView.setText(stepCount + "步"); + } + + + /** + * 定时器,修改UI + */ + class TodayStepCounterCall implements Handler.Callback { + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case REFRESH_STEP_WHAT: { + //每隔500毫秒获取一次计步数据刷新UI + if (null != iSportStepInterface) { + int step = 0; + try { + step = iSportStepInterface.getCurrentTimeSportStep(); + } catch (RemoteException e) { + e.printStackTrace(); + } + if (stepCount != step) { + stepCount = step; + updateStepCount(); + } + } + mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH); + break; + } + } + return false; + } + } + } diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/step/StepStarter.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/step/StepStarter.java new file mode 100644 index 0000000..895a9da --- /dev/null +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/step/StepStarter.java @@ -0,0 +1,28 @@ +package com.example.ninefourone.nutritionmaster.step; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.example.ninefourone.nutritionmaster.NutritionMaster; +import com.example.ninefourone.nutritionmaster.modules.MainActivity; +import com.orhanobut.logger.Logger; + + +public class StepStarter extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + // TODO: This method is called when the BroadcastReceiver is receiving + // an Intent broadcast. +// throw new UnsupportedOperationException("Not yet implemented"); + NutritionMaster nutritionMaster = (NutritionMaster) context.getApplicationContext(); + if (!nutritionMaster.isForeground()) { + Intent mainIntent = new Intent(context, MainActivity.class); + mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(mainIntent); + }else { + Logger.d("已经在计步了"); + } + } +} diff --git a/app/src/main/res/layout/body_information_fragment.xml b/app/src/main/res/layout/body_information_fragment.xml index c5cf7d4..6edf236 100644 --- a/app/src/main/res/layout/body_information_fragment.xml +++ b/app/src/main/res/layout/body_information_fragment.xml @@ -31,9 +31,11 @@ android:layout_width="match_parent" android:layout_height="100dp" /> - + android:layout_height="50dp" /> \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e7b4def..7691e36 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':todaystepcounterlib' diff --git a/todaystepcounterlib/.gitignore b/todaystepcounterlib/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/todaystepcounterlib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/todaystepcounterlib/build.gradle b/todaystepcounterlib/build.gradle new file mode 100644 index 0000000..af7f3ed --- /dev/null +++ b/todaystepcounterlib/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 26 + defaultConfig { + minSdkVersion 19 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + debug{ + buildConfigField "boolean", "TODAY_STEP_DEBUG", "true" + } + release { + buildConfigField "boolean", "TODAY_STEP_DEBUG", "true" + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile 'com.android.support:appcompat-v7:26.1.0' + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testCompile 'junit:junit:4.12' + testCompile 'org.robolectric:robolectric:3.0-rc3' + testCompile 'org.mockito:mockito-core:1.+' + compile files('libs/microlog4android-1.0.0.jar') +} diff --git a/todaystepcounterlib/libs/microlog4android-1.0.0.jar b/todaystepcounterlib/libs/microlog4android-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..d6fbe995ee4ba97f2d8b1e08f1f035d357641bf1 GIT binary patch literal 74651 zcmb5V1CV7=wk?{Kwr$&XR@%00`=l#vR~nVJZQHhOo4@*Xzkj3e`~Su59dS;a6)|J& z6S2ma7;8=iX%J9oARtIcAS*i(KA^u3@ISu(KA?X;#`d-hAV3QLzcw)cvN5-}H@7kU zznUWcyQ#6g$^YG+=>Nms*3#I?-p1aX)zHqw$==fB-(G0)_y03N4VSmwO)NkVAnP7LD^IMrJb@=>1+uH z=24NU4->TZRT6g5ypQZc*g2 zigBn1gl-nV*fa#r!JH|aN5;Pit?_t3xlCq_AwX5i=uf*JpibzY>8K?%6^J$8M4c^0WmQ-+%ma z!)Vd{^=RAO=VWkoVOOaFBc;-#(Lk zFyHC~`Ter&GPMH4H;Z))kQv1uDt~E+-DyVcRqrP<`bM&@ z^6-O@z2odL&awI#*z@r643>jx;OIg}F+KolKJ&Ddi%{WWJ1e zHOj=iG1cUlG((nbh%`&Olwkx3$7sWbMp!2G@&!@ZjE89etG*JR$+)SJXe#Xzwe@JC z7*Sr&-YpQj_!}UjI9|@WMOMO^DON6-2ZqP4d!+&BbXSajS@rubkMj>!AId9*T)+YW z`4IvE(fyBDZEtRF>hw=qH>z73pe~|)+0|ui+K?030F&}!fDYIgh$1rmuo(dXkq#gt z-^q88AUC`)gK#%T@iw$>d^*jqL{luUq*S#mC-leMZhVZ6XwrJW)6}l?v20YO;XA&% zFsa9yXlfF8n%d-f&c4sO&h(l}6BlT^#|OF}bRkeP7U%D4nT^bNX-y2hzIUjHXa+cz zBj8`$?LqK=APU^=urt^_3-(q4Uku?hTPg=W#_{UC@gkPMZg!bY1i<73n`$tg^JBqd@u5^}&m?%s6TFQCm`RLZF z9Ad0bEwi~|YYVAU-<6^%t&*`YxJkjvWXnNToq>Db!Pl7Hk~%Dj)$_u}qUcO1S>E=| zW+92Y3QQC@`IQn+Lzm9I6%0T$4~zDvGwd1Mn>1|*1)W{x(&vnidXvcN1*Wv9GeufF zA<|HV{i1YX#rAhI@#I+NU{>bl)Ip#zx2B(Xe$iMUR-BYlxVFH$!B$ zv(XC1^|G+GPudwI@e962H16;uqqFA7FqRx;vkOMlT)$(I>Nc4NGTK83Y-H|IfXr~^;dn?Sa!Vd53TTo=n%i0=Nar)yE>p>1qo4O0Wx#0K zXbpQNYu#&3aRKbCG#1B(cv_%luslbalu*v)9%cp`_w@~w&dKQr%T;nHkT^gT z`(bSsfM-QWnPV4iP;<q5nGp3Wa(tL;-|0}Yb2w!>l!V7RXp$Dfo0sf zC!{%bcvp)Ni~U)`VvI(G1uNcTMc7ik!QWE5K^S66QY^NMLb>tVV+LC`rYUp{wU#HN z9$f4AjYA&gl7b(<%ULT(h9Ds^C*HK@@dNC79|WIY#er=P|3kW$>qVlMC_pNnftOeu zsTEYLB}jWG^_v%tk~r3zj<@9}l3slEj*)j#S|lJy9to@=3hpH;K$?@d$~ba7NI0eHOeDW(lyD^R&^@DnC^P<;N9xEA?yc7l#6L#J z2#Fr4d10kduGs7D$OPphU4Bxn`exVZ8Zvy58N?CRNI43Vb=NsXG%VGy+EQe{crPb{ zu)_(+1r>p{zeahW{K@1x)t@#+W^caYGSo<{%DA5)TYpEDW-aU&G-{F;c9`%&PPi_3 zrqU>9)e4CA4qq_H?aC1EE4ddaS92hPCJl9%Kv8JBl>bz=DNIk9=({9nl`@T=Q%-Ce zJQ&jD4|(Qy^C=*$EX}One_JhJNIBzeZ=ZF%zqrWk3!bz%z+ac$7GUy3cf?5!8>Ccj zKHJbz{L~&r8;cuwFcqB``xYm=M&UG6snxX#joOK7+ltd6@8UMXMvF!=^;2Zmtu@YA zSW{BgmY;5WJEWL@E-bGt%}v9&pmF5@Igc~VsN7VX!Y7H zgS-lUck^gS9J+OwZvmzU&Wr`a6ef5nvKmi%!d#6f6&iOg8OYEl|5aRNFfDJx3$ zNifHT;=ybNpoDF!WY011%6robXD9P@M}nmw)Ik z+m4BQ{7H|=%bsHh9Aam>*A12Yn9PNM<$5yuATMJiivuOM*?`=w&@kK7I6Ih5}ri8v*4r@r|jqjzIa1$yK1Jk>Uq9 z>CZC{rr@eMP?!^&HY*^XN6l7B7sP zyzlKTCnj2shxTU}ELjw$B5o{&(G2G9;-~WU+z$9k~hbRml1iS-I$asD9 zRYwKUTi7>PXDEz_6J@KS5pjhVMC4!%!iZW4RKwc8tibTCKq2Fd?$VqpluxEPpuH!ALRg7wu-o!pS5#8eiE7+cLXvnLKjL7$lkQ3aLif7LGc)!N@=ZE<*!WR$^+v zEBs>q4*1kq=^_{oNm~%b!V;Ac^iPdIeq&501Wu(jTOnfh-=bh33U45}QW{I@gGM4+ zd|fp)d|GI{X>sdIYPSZ|gh986GSWSKxTP~R%zqrdgpj#&r46-vqqRHg%n-c{)Q(_8 z&K0gJ?=vLG3`%vbo=;)Kv6OH=vgGB&rzhZCAuL~xoCRp(Tq_cjLg z0qKD5uU#Pt*f5Wh1JP(Zr8RUD7jdPNh$oa}mkVmaI&Qog7jb9$fo>w(T>0Mx4{>Ka z={76$|6XJrTux!1x``J^LFXGekHxjU2mLd;)hSC01rdEC|rjno=aaz-u#Bs;>WYX~Fo<6}|gipG{ zQlTV*@1e;a(6~h%8xW*Loz9WsjpV+?RSQ49TrwsyRhe4dP{SjNgm$hUsvDFhJHghG z9x2}Eaa*-WLRXB;KKo@tRiRBm@JL^lm&Ud)+INRM6qZuj>j`$ax7e^{a$$NMsz)%VHc&i(qmr~ms$;*pl_nD{TvnNG)2v!gH(9#J>F>L| zjycubg>xJmVAwHWptxi5(GGgd7jVx{JqW?6<;@9Q@k(OgajTp0!3`BLrEIhnh!)5h zA5h|mAQY(4x>b)*{$yYS9W3!)l_A|0BhFOD1SNes`Aj;7^9A?sWz!lB`P}|rHD>@E z2#EE6UN(st8oSs#dHl0_DpawSpI5;2onk*x8?)^>D6)a%45`Zb1-(xm(F}$nCc)}^ z+NLDcmh6%R-7Uq6G$SIA{*oVM-@XELIpy)*G5y|=;_UnR^@i62i+_9alRCgn zrg4}3%0VKO=nm3wjcYo{F=d!<;w~uFk=B{_$nj4<2s{E-n~Sn4nzb|+qjb8hRw(Sa z<|=JOnPpiU@tBz)Qe?VK8fa!xRDh`!*zDb|QPY%Pd@fr)R(j_IqKw=XW6)A~U@l8J z)|gpqs(OVjRdpg+I-9BFc0BDQm)B5SXK@l{5d(%|cn8s_-bz^63K~woiql;g=t1l6 zi7V-AQpFTl)rr+)vy#Zkmr&T!lcV{bzPQpuz2!osQmQj_&6JoJ%r+biOrzs-cfd_^ zNay2@jMXZYf}AqkD7`kQre)lrLAoM3{bkZ+!0VR5$t{gOk9fKIX>T_LRc5@aeez4D zW|N7j@I85P(X%B5%8$)3=o1l)P_Q47m;oU~9HLlM82M7S!THTpq? z+gc@ycr=qfsk^I3*kxzGQ4?~rup3O%zaCH8Ep|@J+?FOmLBz8&u3Vm#coN#j z)$Wi9Xxf!Do6Flif*`2Eo}2Y}kRR+^XQag`i(4D^E#y0k>=Cj=w6H4aN>OTz*Fa~a zx7C(8qxj#T(SVFv#H@p`xpufHHUv!GgL<$6UCmGi*r7M)#-Vx3c4mFWfZTz;l0GLGI=l#Gf_3sp=juTAKH(8?Jx}Gcm#`AzJa%{ z33;Td<(1w)xOmvxOb@UE}rlrwfWFNpX|`(D!Fc zhv}R}J|xdHd*2};jAjYFMD;l0tx$Q)Ewa8s{5xres;yjA{*p%Uul&jQKOv2>i@k%o zp^LG_KM519v~549fciy`3a$Ifw1KL4?4X=0cNS(pWmz{s*>4EkJAk1))_AT|LS-86 z7vbkGPo55|>wSZ86U&^fZFL%k!2ht}JjM6?-sLRk_xW{;(nFsPjjDpS(Wi#4X#?Y| zoyyaEJg2Iuck^HE zY{f{Ud~uy(s5F;nXl3qLvroD0YJugIMO$J_Tn0|D%m|GP>sggmK`~xfZxC7q4Qv-J zxHTdIR(BBOg4yDG8%o%bsECDgx5$rZ@ssiPHO!EW{Vk>om`jl%|KQZoswIoLU1Qu*Z^Kb0YLf1BYRCnU^C4p3{P;1(Kd^Em!I zLdH?qJFIXX;j?>WyFdq5&()~iWReYQBs+$I&D2O4YtZDFUx@`T#Gc`1kQYdA#u3kw zyYra+Vgn9**!}Acu#2sC-SaO&QORh^qtI&1rGpmKz1Z0(qcB=SME>~k2k@1*s!hyo8 z<*ChC$c}%pE(-<(^z%R3NzlmI#p!RH5d7EH|HOWlvaUReASxe-c5+qZ&(|Am7~&>T zO~GK1xFF0718k;TqTc39H=U4)E7p0smwfLT5nwuFSh|C zuvBn@mw%Zv1G2xGQtdSLmwVYl%g8Z_)QT96n3UGpx@zG5RR9uzhaJs6i(& zc1UkT*z3BQ%6{9~GAU7O1hjYSskPyf?Tl2DXrke(5uR_LTZQvw0vr5^XT7P80t(TH+y#+VTLCS?y&dYz012;k{&^KOH1u*m^{Zh zkr_*%d@>NQ>L-g@Luj58$(A8T0=p(G(Og=UIVom63YG*FF2AxXeI9%RAA@V~qh>7h z_#INtiW#L?o*4;@F^f5)tXt%;LdvCd$lYCzFu>vCR>vMM`@b~Pxq4Gb$PN65M3ovu zR>pt3f7CQhTAPV+^hc}kY*MbBliM}AdZ9+g$ehwtOyk{kq0+iTO*TMb+3inp4SxQS zL+j?zwqZK^*2BIp=o<)M=q0gD4B8L~pW#Si7I=aAFJk;dCdQL#b~gF}1eEX>(wzSp zoc|rhO5<{UKT-KeVgrElUX~DkMWaNw8JdD3!}^1r1w>=5S<_MBDS=-QfX8+7xzgV=dUT^fwU#(gO6;USi%gwJA^Sd~I`5J44a0!T}(a)`dP8GQsA?r#21?oF4OQ?y7fsJ*}H``fARccRC~x+^8w1 zsFjW;JP3KcM@o67;@*44BcSk#?O&*bR9$X#RBRU|1*JU za2|PXgcJZo5KI^?i{YmgSGy0>%K0mw*^7!~Lu zte8F61Jsq=q{f)C%r4Bo*4D~8SJx3vq_^AN79f_XXP2t@-)0CFpLom@5Q$VSr4!lA zuUF0gbawl2d3}AHHwyqg?D4?y#K9ZZVJYe;FctR3HUL^nc6D`-H%!sy1G*%i<@w&BEb%k{c6r!1=gTKkD7W)jph?g!4$x zc{5p-9(HVR0XM0t4IBGWluK-)EEJctOx5z5qR!r3jKaxocP?0;ZhFfNIFk zs|rz@VI{2gxRF&idV6wa);LNHtl_Ek)<7|)^A$CA!->hX!HUxbAXqg}V&IGl;LC-| zD6V6ziR^%%OLD7RV3DH+T9o|?;G>qjna)=b=6b^JO{%YhH ziTzof$_A61B_X%qBn{))C?G$6^Z{Rbh;M;?%C?JUv&(p8`VmK*!VdB#7iY^%2-ew zRyW$E^JsB4y0fo0FUIF&))A)Jb)z?wnt#p_gW#Mq7zypqNlWNEH~x+?-k)7(FdUqN z;AWEwrONE2TotATG7pQbvGJ@aq`+JA{3<-O;BYl(oWk6a7>1id4g9kgX#MgRv0+Q^ zauTvSr-$zCbaifCPGu)Nl6`stJ8M>H4RUm!lc$!8Ye4>xmNmF-q$~m%j{TUNVz{-C zp3@=orcQHKz-}9`Bq@3~j;8Nzf5X}kOuK*wG6em}4go>H!fd~6HqUEYQ`$)E!`t&-F z>&*QFpgksHs=_7`V>M7FcA(iFBN7bWRoDk0o0 z>@f1OUKd`j4v}rRKnOAd2VL*81U11#kMr)Kv(#x5{%v$Bd1g8onkrlLfjDGjDuui4|TUwgE2Jn@CV10Z5PP6$rZ)l=x6d zwT7#%sYk!+HHKFkQ1)eZxlW&WWv@gK#nZ;(7&z*Bex$lu%;hb z`bR@l=7*8AkxJ9hs#dwnc>2AjoVEhA-IA(ADVuoN5^jDHzBP&?Um&?en(Hl}x=%GI zJP2hiEX+#SAa}bh9JRoZkjIQCHU5X~%GW)#i~D)Si}eY)hQalPqlbG5-gXVtmefWIn&B^(eC?|&3o#Vl=1|C@JK(^kh> zM*E71XEov-66qyh87;#tu0_NgJiw4C$5cus(y6O7_ZR}>==PH1wZpZr{7 z#o$TdKPt6uv>x19(!IpdEua46v+Z_fg(R9sqhRnKY`t)P;@|#y3VvPo`+9Q+qPT%T z8FpX~|KqkN_aOr(z`^w)RGH20h}x|Zm^Gk;EQ&%V0WwZb`x~wz^&}wQE?au9>kudvALq&_tn;=i9H4}F1i->GHcwHt7`RQU7xMa0*oi}BgTw~mpC zkDHAsPRC7$%T^tbt^t>+01ga+-GnRPo*vT2q9x-D)vlGKC;2n;@W^o3xeux9EQ zSoAB|+KRpdFPCmL?Wn<&o>L+45r(aMFFbBzY*4MkU?~)@0_9th&g%1c)dZR)p}J#) z|MlCUmrXV5iyYN5Knc8+*D*#6$0+<&XO0BSuLxi5oTxL|O>{>A#i#2s$_m_;ruOG@ zj5RMa#Zp8aHg~~9&p7aH9?j*?Y2Rq*+XVfaap3Z_^U^Lor?U)Qnh}^eTh%O0Zi-05 zauYkU8+9~O3bf(TymJYLOx)u4YS9XblCgJ0`3|Z?XeWOE$rwys${Nmk#`GgH}45}sc1-7 z^S5>51(M~|i0sR$5Z|8%^|^R=M|8G(jubgCzBjbC4A$C575WGfb%rlROFGy}@*1=+ zhCi$$%W@s)8W_WTVYoI-4bh#^^%Tpu_f%o3cU3*v8(mu$Gka|BvxzD2-cE_@>3>_w zR~30MROVm;Hv4b@i8C}w&P#Yc6NM-rWbhyKDKmE3fj7kr)97Nf-CCzySbmyPm zz+!BN4YtY`%T)y+O)ZP4LA1%dS?7fDQ)qRDGNlB%uaH9gr)j-o z7)JbP$i|i#z+&v@OQn3*dk=f63z~uD)m;z3uHNnFM0av=)Nyr*>)n_()a<~mor;2G z7H!R3{z#3m6{9b?MAvv+S&P_cRf&~$J&N$TY-HN81ne1eELgo!X!fLK73RT|*=x7t z!CP|12lis{N&r7(!i(9IwMz_MMc&}D2t2F_) zDW`mIJ@C0@?Y(l5|82x-{`u0l5#-(r+LAg*;HvxR0bmKP%W<&1mWH!C_$WQ?1@7X z!7l(Rnp@U{f|EZi)FK>{_b?b; zE=}3QW*pLQ--Mg_iWFLCA(In!;j!QwtGT4t)nIRLmUz%Il zI}8ff#9ZEz+T1HGn-4t;g9H3B1b?DzVno)FnlzfXt zn)r{-sh|4QZv*>O3yKFGex8kR+jR%wql-PW;p*a==y0<;3_sk8Irx?YIDg37&U*ZD z)^3%A;$5dzGa1VpVZd`be$ofBd)D zcG+HA!}7Oc{QBEA_@7lDGWO=ehX1bwLdx_n5hTI>uVRim^rzi#Q!EL(B&>uIjbnC^ z3Nb&#BxVY6+^i{_0kB`#c`DVmRk%K5e_+kVG=$0jYkP>nfEVKkT{dceI4t+vyuyNm_AvF;31?tE_f=j&e;;2+ za5u)hWzfUA4;p?+O;I1W6l|;o_0p&cEgRbTbg72-Lc*h0s9D+%*f%%S@D}VlU;PKH zC`pv)Pmo=BO#Cl(-*Y;wm1`!;n~}zKrNs;Pi7;WcIY3Hihy@Fbmn zy}-IXuze$iBb-V53Q;ctLKp6hClHG$N|4ORQ^^*{Q%NS|809T8KR)rX4j4ltC5-XM zHHbPz+iT_8{Gpp9Y&(8NHbgHwNTQ_XJ~0o=W)IU2%I0~@aTzImF*Gx|J*_kjPvNwa z(ksX)V4NkR(ns5kDo0a^iak;Md$M)0^vP!X7mRm*dszAZqh#yf0r^ij4yx+fKuT|&r$U}_QWfOAl7r58Mq__G(lxjLXkloRe)*5hZEF7Yf~6$yU)KtvYEu_sg{ zBDw{lMg{D|L-?XPn03O*Sy5T)*SK6yOl^8ybslfweLp^5`vF<(d@{rLQ|%&7VJ}kd zLh3OXqdOaOhH{K?#=?l{vYsU<-oN_I{Rswx`5XGAfq>muAWd2h+7(Gj{QCl+Rb-T# z+&py&n+tf9b|j?1oLtMBiTbs#LZ%aq=`LHp+Hk1vGx3l1WJRN->Exd?tCn>2x$%Ew zX5R^2o?m6+xCm8p$E;tE=a2{&7q&~=b)@o7!5r}Ew-GJJ6v-O2FST86IpS@@waM|o ztIlNkJ(!q&$I*F+zx7vH<1`myApF@_gMDnGGTmCxd+;U;Z1GrBd6y7T!)fU(urNih z?aTB*5zcX&u>q~fjv{WRaUYgYCb6PrWMUQuUm}&w@xD}+e=;r%9O*j@`iX~By^+L( zWm7(FGADbaZg*ZelQ=%cYVFPO=!!Bh#1ysO5+I6WZ#+7rs#9NMvyZ3KJkwAc;7-?I zv(Zx)&6dB#az?HOM$c=x%*6!ZuDcPoH}jZ1;o-hv6-Z0EL4C=(>;^DVKj}=aq2WiS z^vUbb#u+AGx%jHY>$s#9a4{;!UVLhb=$yo{>z4XH>*==oDEIm-uk1xM<+p>D!Lx}= zt%c{IcK9qm{`x z>u2l+X5g3OestuYbUM@x`*;8eBWN8p2xyx?qa+H&&OVyL?+7x#p`Do2T>X(Mnay;e zBLNQ&#}$D$e0=lKqd0-wHB#?2{~X^MS>0;#J+S#FuKd7Hu;d#$*G`FM*69H?+h~^pCS!K!$8s2 zsWt}XWS8^}0QD#Fj3MoWBek#DWH`%+VTOLY&dcL_bHObDw2lt0CzLO z66{m~ilTh2(=gLhe|ByQXHbH)A~S$))|EF&2AMMF*_E%F$=sSo;AIPQll(XjSQUpFeb_q6s)PdF$ z&%^&-LNwmxch6-R9xE*cB(A)sn}WJpe`EhI?VmQ?DQxST$XJzAa0eG6z{Z&j-{pf4WXrtTxY0x1xPI4&;{m-rM7KY3P$` zh{YZxfNDio97ExuB+1CrT8T7AMo=-j@6#At4_udmpqwV2JGSXf^45y$^%sS>4z$)4aQrvzl`;>S+6_XCTOEf zOI2Wh>cx0+*1F|Al(6S_al^jom8)H0oH$P;jB-t*<~{~rRhR6==12J3&T2f#_IibG zbJ>*q^wXv^909y`hAyQ?{(D;X(v2J8{X-(Acl%oJ9jU3jtAYzK0SQAQ-aYbOuwFqyl`V#-0fJO%sc1-kELjO_f)$W7k952UFj3rHOr~x*W<(MT<04`$KU}PzAy@=q>}@dIoV?X z3I+UpW#W}3LYuba7Q$KPm81hCN}<(`~?XJ=pQ3A z|CpNj*F?}+Um`mg*^-P#359qFse^s?&Em3Z6@ciB=Q zVQ$2&28BX)uH$F2sdx;2rJ7uvwBz!fmRcMcNo7J2D&%kR+`>Y--$e*>4gSEg(sQhG z7U{pyEpj@jg3+P<#`LHBojG~GfSlb67sb@PI6r1id|B$9yVJbP8$1uUr*%PVe)zc< z@?9m~kYRL1z3aX}<6HFoy~nsE#6SoKtbjqqI^3vsagPn=>)`J1G1&n&g7;t2!>Ze7 zA}9c<_70C6uV>mD=aTU94!6I9id&D?iRGj?4~00g;~lj6>gg+n6rs#TXeBslb~A&n zB>-}L9#GTLErh#yK^Uao>V1*OddRnYNZaNXIPftq!~NlsWTipI`H9Pedr;u$Mix*9 z&Z4@Lr(TOTlFmSIaIl+b&k_oXANjqbxwrB02Hr|gvY?}2p;+AFAN~rQza`|Zpoa3l z90rr};2kj(P{&Pmazp!jI|aw1+ZjXL#>w^&g1Op782fDDp73NI4{8K3!$r~T+!$R{ zCwqGU8)1sknVwh1Q)nydEHJMF@vlqv znkXriVb3SXGgaitL5nJ6EH9nM<{KoZe8^OzEwh}A;#Nsd+f6=}rlMd_uS?i1BxvrV~)m3sLxyAnTY`k;Goe!ehHq;XUYnM3Yw8IMH209)!Q>MJ%j#$Wgg`0 zgEvW-29`23q98W)6nS<@MNrwbvI0}hHEGvsd+)dBndxdKH5#;*oV*EuXazIST8j@g zBmBVmjdnI;GNmM{PlsH6Zd8!cs?^)=Z?jlR#u=yn$iXCtCB4;Kn5$BB7m;41| z8#Xf@+BvRnZ-(+(M|SS&;EYJo=ApCggDfUe%NTb>jAv=mbE#+J`&p6n$0MkKx}`X^ zHa~lc5LZz{e3YX^ifZ$p&^SdPBl@+d5u!mAR$ zO+eV6kr9LLs6Tt=%)T-DBQIpWgM;PFbE#;z)>zfM91FsW=sEohFZJC?x7=HCxkge$ z*Ee2YD;doEK^0M4CRo@_9;P2Z+?gt>m?2H;wX`PRU{2?7@oH5~%M4koy;QG%uw|q@ zsd<+Fxppy+uAQ^}grz~LPj8-M-D*`*d_Ed+mbyQZTBYTJb%Hoc7j1IY%c5PFwDd`K zc@vZ!_uzPVw#O|PRn}%Idq|bS#Uoim9fc%-f&cdI)wsuDRU5-%9)BdestNo(&GDQ?d!#PeI z220?ODq`up{MkbOQ*-qM=GF~y!U+dY+#bBIA$bu1QGF9E6sg^x9uc64{MPvlw$)p? z{wybWVeVN{V(siLp?WMsnmBovD?TyAxy>}CPHS~Yv|u)!?EY(Z3%)EY4cD~($`9Lq zQL*}17EW#UeZ>>|*m_3vIw0^tuRgx}j{(ZAwGDt}mu|&s*BZ*ce)lrSedR{3uifUQ zGickq-?vYg9hZ>TreCm+gq>Fk&+B5?`1vA+^F_ETiPII%2*>diGbmn`n_p_>J^j1eQUcp8 zJrBI-bXZ^Z6^L{-IP>!3F6dZNzn35+a*1_V@~j|Dl%(?4)>geiXLg9QL#FUP7N?ga zWT)LN?7-H2TtX$MH@d9(4!7w5ReT39@F=UOM31cMS&o#8@&Nvx>o3|mEG!7e`r9LK z$mY~`_KQ#y*@Cf0ewdWNNHA80pIgHzS9mZMyEMIYlXEG6dATaR+FxWvKI*Go50GEZ z@K;1y+|&7Nt!K2nFc$m#(>YnzSuiJkC-3?)}k^EVa343dYXqCm~b!X+Y z=njLD=Lc7Vs-y^WrJF*N2o}ePudhG3NE|61Lqh&h2XE zlj88HQ%M#dZ%QfM=v0!5PHT}-d3a0B(E&UqBC_=o5{hF@63y`>Q;zvaGx$EoEF1nX zJ5?<0cqTk!He7!){8xSS-y4}I|0ijavAvy{rTPES##EMy4%+>9yx5rn2uSEZ*hj(1 z-oez##Y6aiZR_e}__yu#pE+2e78F2d(Uo5u+cH~BK5{rY8vWw7N;mnI8f#c6*~ZXT zqAu#<7nZdAUA;KV80`=-X-E(vvJo=mj}7#-mI65{#3)0PeQ=A6RKzv2Tsl%B*$hzO zd3Gw>1ae!D*Xb+{dodOgX*+&D=M?8@zIVRkEegNS&Esr}JTc!MQD0Az*oc$&xsQBS z0f+e5FG;a)+DU}M*LYTeseF#pyr?awxpnN%$VivB@Wii0u$gYCk8RTjWsJ|LuYS?% z8NarZ?vrES_h8m;mpMP{2O5gcHBr-t5V3EP&u^;-zqxO*ba&kce3U0<9xmy9n`k__ zDaToAPY}3ctBc=y^PKVuy4c5otmkv67CI%4@f8^3-r1~nmPghdKYPgJ^eFE|U%kOn z+FGK<*{b5cq$n7c0O65Mb;G05x(mdx&3DX}$;bE5dQ_8T-g=Wsb1G0-R;dxvIy(K0 za|>Lpdi&rjzn4ck6e+7+?^Lm?o$oxOIXFG?hcYrdH22A)GuXEF4$8r-%UQ3-i|MzD z93zL8EjG$--7uOR+lP2qw@MvDhnB56r1#~b+cP)lCcH8?#((q8)CG=idW?7ttnb{v z4P(Fc3(gBHO{mOZ--Pz*`2l9mBQa$T*Vosy-Q3|Xt}o9|ZO?H|EY7#Lw`NzDHs= zWZ%q;sU_3xa2ZjXp~8m)9q!z9Ko&lB=pkT_2}zUU>HLZ}=)tvAAoOd=WoshE@$s)^ zB3Ut)0TnXiM9ef9^RuaN%yQcf*^Dz7NK*9qMUSOemduoks$mOdUrjRdPZ)D?hvwaW zkQeC#!rXE+7>t|vDJ%{Q>sb|!Nua#Z$w=ktY0Fu&k}8&4e0fsCgv1WwTVa`BG=1YW z?4N+aD&#NWpkYBLS^ona=WP#irNm2^h-BmN(QDVc$P|mCfppg(Hos2z6#Zcfmm*kr z5f8E%g7^zbn-=l<<~`b=TpsWK?aTD`Eo>p4LoX=ElR+l|q z6ftR6&|{#~Lun~Z!*Y$Li?M=#%`r^jE#?PM5^814#xtVFaMt=I#}WjhohhP7IJviF zDQ@IM>`SXwuwy2Hl|abak~hz3^s)o==fQD_s|&Ni>FGr6UC{a~hy#;ZOUXqeo+^1B zZFaAW_@K|fKV-W@bm&krwRRHZR@7#V6=IErvP$^2r6eKb?|~o-9EQ-6)lHx&DI-(j zNt7<#>SndEk%*)1ZNG82hN@>B)`FwgC&xK(Z`aANhM!GiYHM)a_TdSeNzmMThCGsgoe`1GcJ zvQz=wftv2ixo26@59Wm0pESXzaAYsKtlWruM;1IH*=2!iL^ zg}~6!@~5Z;P+9mnp_#k|_j+5$2hZo;AloMgpWNF63J*+gELqt(6pmUfzv+SuQwAIP zv!aDGexIdV6Bo&64Srtgt#}NezyoULuc3sGTi~-rxE>{RE1oY{KX=K4Nu1?R3<&pd zkP=gCv*600LwHHdW-yb-W5tg_c%#zG!AZESu1gY!T|83}&TQ_8ibBR|&15+2IK+Q5 zh&h6!EA9baW!4DiK8yaj#E*RFMO_9&Q}WM z!Ua)1eIWBxDmEwcK3Yv6jgTV(wR&756Fi3c`XzUK!j+|$gW>vCWJBA)QFd1192c)1 zq1-Q3ynNzH5$YOqgG8|n+Z2UUwgu72XJw88Y?xr*+nZh6k$b|ryk~O*v42}tqZJ@{ zU7&)MWq*ip`eDd_bg=HymG#N(7bI|Yz~l0jTCp)Y?DAHITM~Qjjo~MBEPuyT)R>~L zco5CXOq?9UAo$V#s?1Grta!8ImC9SMimO9_a$6v@L|(+Zc#tMW>MUy1R>!k*OPw>* z${HT^2tAt|*WaKR1=~PPuey}l^#5@7j?tMf+xBoL>DYEUw(WFm+a24spV+o-+qOHl zjgHlC_C4qR@7epF@s54Zr+ll7r+%|mtuuI?lrie-%G7ac+%25_mt?)60Z`TTT$a8nH&j zpb7D-b5DZipD?c?1H{!cM;RgcH&AWzarUe7A7j!cez)05ChX@;g{ld61c$`6(!%2KTH4u zT-s8vzU20`>>N{Iad_2hU zkoi@J&f)BR#R-FR7u5F4nMI>wA-MN~t(th;E79y(fM~SRVSiVS?sV-EG@o~WVWO$1 z7<&`%$%Heq7f7*A)W`8&L@m^!^ZjfZ@i?!_IPe|fL--z_Zt#f10je(_zVJ3Cbu5EF zfMJ;tP(H^;QxdHzK=j@UJMKIHCzi);ouK&q`^CRj4{o=RXGXY9*#Vj;WjEw)G|wa< zNdfUW)D$mK-j_7w1SG{sp5kCMA7xJgiln!k9-eHHDCL`Y+E&E#Y{9e61X=u{1${S( zSr^bRc+yFmL7Zu~!N-)vE#v#<@-8Wzm!59H9UquqT*Z_1`<2y0u7~+_eGoR5KX8W6 zLG9&OUp`JLcF)VHvWhB>WEEEC6C&jYSBppGYgr{YJT6Ce6|dga2=<2%GB6#-^$P-);4L+HN+Tw5R zd4bu0hAO{694L4_t`{;VHqXtNd%xq5Ooc1yJ4RRT76LsI|7MRaCBFlGRB8%wippmM ztyJO|E|hNNJ|t26Zk z$8ngwD)R>1<}r7dvr0g>otft;&Fz=aI!Vp;6=j^xWl(51hEb;DT+aPcVr3RSxC|rL zhHRkP2%k;h%xfT)$f|E}oHmQ0%`vLkM@?cc{{idoz|zwEB?c6s47f=X47)NAR8_FX zLXQT~i7Zg(%Yp37RWA%G!sX)$JUKY~_=%qTS9%BwO%L?vs4cy1~Ltmy{a&Udq_&hIi8#;(3zi&76qhi^$(C8e1m$0c zk#g7M_H_2FBj#4CPFE?~BsknNsVmR{D`9%6P;cEpYc}7XZTwG~e=Mo)q<7cLa*eD= zC96q6#|CB#EEwsD@@A6m6LT8i)?w^pey`i7iXvGRmuz^zdIXSLdB(C*)UvuPY5OWo z%YAT|_uaEdOok$?`6JXP=Q%oYubMv+OQFqgv*0`~m8Nt-Uv34AZsxGQ@W+8&G=W_t zfzb-dDVfPd5Ho@Xhj)D5OH?XZJ-l`zdv2?oN_*a0C{|~jvNSPgY1aF}Kam|l+b(AE zN99#d*+s3?8;%w{Y!-Qpuw57k1CN90hoen-J0a!ic5OR^8}C@>&SVo@&w30w-n{Vg zLUJOTaw5li$QQ(LePnb2YQOmH+DZ)&7@R8dxTCkA@A3-$7tnKCI}ULgnVc50@^K@M zX?lNQP}nOf+-9}k4R+vFyAlX=m0pp~R31NV4#8Be zSB4~FM08!ewO-`q@Du-3KJs}U6NV^9_EeqB)}g>blf0ac#96GUj=vPklS@_?7~5q4 z(>DU+;|i?z#;}afpVd{CK{tG8R1n@B1V$3lWK-fh5@%DzFlv91~IgnYvI@;bi-}(BH;=;B%JCmT9hFyrw%3jZAN}>_^i#Rd) zFv(QDt(KyixtjS1=VCgKi@Eh@!P85T01fdaJJgERDRAFqxg1^U!V#Gz23U#PX3 z7A|wY@V*B~)IN)i-Ke?2hH|PPv6i0-v!9um5z7ZUka%1-733uw#81g{Xog+6oKtOk zh4IV+HQNCW4?I&@VU60cUxRTjXFr${jDUu|+9G@j2BinBf%nydwiWvl3^X$pZ&QL1 zWd8vG5$vb@c-gj8n721JU*3DlAlUo;s47*LPo}VPL}^qDfOoY<4Pr~ zKN^gAT(BL_PF=wdgyrR%36v0G48IENAng*8#*+-mCSO)1Id&GDiM^@jg_a2QoA@N$ zv=+i3=oih9KHhU2aq~=Hyg#3vAmQ>F*4q%M4RLP{DtHJPa$j z1z=NOoI_L-zBT3_01Lj-x?tx`)x=F|gX-$1+QQ%p4^?+x*WJNQNI=XRayIT~%Tq)w{O+o$SIM|`V0;~WwQJTUd; zrGzD_ZzkH$Hakq_QxknKbW+&z_H@m=`uG97L?1$M8r+|PIJQq}Eu30`92$nn?)zZ; zZ4~_(q`)-CN0<}vCa#hyM-xdL`!IW+!Yj@ivCBFcXV@<$#D#?!7vUqii@dza!KE+H z>B4~a@aPTlKWyY5-DC3@F|ahB=nnhrL=ycE(5+x(YvW+{N9X)s@U8cU9FWK6?57n9 zz_-MKobZ%20U*R{ML=yJDTXG7F#+%Ge%7Wjcb{5Ca-$1LYA(g>`5=mUb%v$a6EWa4 z>T%Y7wC-@(Ir;iu5XYXGm#fL>MXo3rDRE8Xhbp&sOB!jo*E$$nF6y?bdl!q>8Ix&+_5(kiY0OJh_Q*1HvATN2IoT4dEn7xkrHUtn$c^D z@jYY)CCSJgof&Y^$Ja3UDmYVZj6bL}Xq$%=O{=Wt65wqFOkx!)R73(iO^@AV`7pL zaKEJWg9qG7ZylMc>@Hd}rPVUYTwFH7m3uUD3cjoKH%hh3turpI3t`9Q7~ z=lJQRZK{|i%O1ji1(Q=VDGRYLdzB(<|cFz+QA7U=@SKVHJA%x z9MXRqGNWve30~67@b=Zv82917y`ytP4Q~;BeMk7^o3VdQZyy((L1iLW?ViMDlyxn* zZyzW%*e&?{)!DtWPpzOPP;}?xiS2(7`PabxxA<{m8+$7~$Nv?#^EMOtwm%V__SqZB z_P3Dzk99=;>yP7q8|g*L@k@a4BUH%jR*_Qb+=A7k2m3a!JH!YFeWlAHt-ol|bJpZE z82I)?3eVdk8k!D)CH4Jv26j=|QMJ>jqYDUQmt2@`kbs~Sxzduj1Ln3>TLanhVv^Wg z{dJbrK9;LA0{zA>GTvWWnKKdpWBq54gnCLwG7f8@YCT7LOQ_V9a@INZHTfDZKl=?% zwKAUC-mk(+SN>!8x;5>E&D~%L_V?uAbHGgINIoZCvLEE|o)cK?y`?ZI%x~uB#I$EL zR@vXI83+(G6=fR)4YNRLt-*#oisx+Def;y9$KHPtZ_PjwdoFw$qt>DSP4U)0H(yTA z@pH$l|FU^ zy~Sg~+9qo!C#~8z*qp+~mcx=c5XA?fIO$xs22K`Ak&El3y|Om4-g7-1J+`hS*AR9^ z7WwR_Ub7rFUk@_qWIbK4_`gVAM+Mf`^oL=G*Fv$~*wzd6%RqZm_C{!T+}0yy_+>M3 z_VrssTi5QYKr&j#&xEYeJOu^XpX=|#L3>l^aXMd*Wul$O%Jq&=t`E-WKwl=&r>h!& zTDbM$oY&wJ>CsN-BOTs&Mh7aoZPMk29eI#Y|6mz*-Lx3=Lc@uv2}y>#_2h$xzpl~j ztG>oBzl}kK|Mdn@Za?BL>gFAxif2-uRin>DBwJ@MtA5=8+TFCxhQ>|w805xQTU8=*pnn{+$PMYnkP<=G2|UkCIH#lk;?aX^&7F&LjjJQ`1J7?odeivv zMa8GOxU+I?ZRNnCSo=M}w}1ZhSJShHmvrk|7Me@3?==QFIijCBUh`IaQD;5bZcsOsc4iylOgStD4^EhYIGRDj>=sMZK+BCjX&>7%Hj|nLxS--3}Uo7l$wR6DyR;>O`$O?*-&t zKIj)2B8*Twh+Mok!Q8*m%oYpjB?m*jTc=^n1b+t})Fx^m@}JRt}1 zwbyAy|I&5T-#KY5zE)~oHFC^j0cOg|yZil5Xu>2spc=fL^eKclF?EUSS43y|65JVB zC^caDh;hdjgw;bJ43n*hSpyn*A$8)wi)7~9BuY}^q#$|+%VX2-{w;C+7ofNTXCN!r zfsVlyGVm}p(8jn&lwQGN1vPP}7}MLBi=dg5Bo0%{&|lMk$=+JSQVNzr+>=lS5y&iv zr~SY$3`9P?X2?QSV>*~|g<>E?4UZR;pl3wTvlu7c{%ILf1dBtp#AMVeP0Xfitj-;4 z674bOxrX$tbwD5tYtMU9s=`St2waKE<)}1sv6cRWzg4k?|mcOkv7<5K~q( zzj5Ln3DfQ^sw3r_D<<8xFwj*3mC99AuEE^*A<3urVLg?SH>0;Ns-v!{?}r})K{PYW z%6MmYfXrdzu_33D*g!1t1GZ6$^!?&BvglxwF>Wwr5`?k6xi^NEV;4odeC6yK={XuP zc;?|YYGo_$*4eeor%cT)DkLc~^7TyM002tVXr2IFK(x1;nqsJ3`7!7?9c7d+ zS7G?IoFr_Zz-CDw4fWMZtQU;gw9aKWaJZ~oQ_@%Bj=I%f-A&`e7fV<_Ec_i&#vr#4 z48w&zD4O8q$mm>ipPxqKE@h2z<>W7@JV!C(6N=u3q+nnb$B3Vfi~6$Y8Q`7s?Y%h> zq`d>jS?7{3C|{O~x&*dz>ua&_{L_!)iy>&)w?Il zdzTP5lLDHNH)8{`rCm+nHicXGz;$I^UEsR>t{v>J@#eB6L;_Ae7L41l8dIwj(!x)u zwcD7%AT5fVDD@MTA_Nwe!ybC&43@$7@FEF?@?k|jJPYlZh{?(OzeH+1V&UrPrGbJW z&bo!Q5(^$qAEdSGb-=Nv!34`G^#3B4GN6A<$Yz0(3`kU|2%Z_4?|so1>@-Y1ALOb{ zK|h}}gC&WI6SQToh(cP*bAyZTKd(6#twqHY!)}t)-{+g=tiViI=g76Bkuc@{RwyU$ z#|-KWU1kS#N*`g=5+l$a+2@4VYM7$bsl;R$Vyd!dm=R2e=!lhsABK^t5nSkaH9UgB zNMb=M2ubq>1B<9sz<(6aeen&LIM!c0V2*gLf*<_{6?K6mw0I|iO5Uaj=P^)8erjv# zGFLnxrEzfFf!iwWnwdp_b8x_mI03c9ktfW|AjZRTUPRvACo;T0#p%(In2LGOTPn5# zfOCf|HGFUQ0Gk#8l{p=lnyB#r;hFk_Xp-?_{?m_owI*{vE`LzIUqThkDAIuZiXxjK zEFLQ&(ZM-PUeyS!$vu8UNyjPvcuj|x6wTV;P(L&&_&aJZ)AJVWOXtMz?+Dfhd|JozcHeqLWc&!2xxD(oQ^8fSU$ZcR+s zy-K?ixXdohSdY9}^F!*33TFgdy$CZWU`5E$UeJy|Pk)G#EHsQIPg4CFhmcEHFg{|6 zWT7;dK;#IKQ&(__$(=Gcdv?ZHq#WYC0+7W301Z@P6N4LVq7T4ABK&j&u&iRB&g&d( zT7@}1FM{Ds?v3-XT?Mm0gYcwX6PSDYb#J^j2Nu4eJ(wOG9HM=kvwnJYZ38A8KNI0?VKY9iu@Fr;rd>gdE*jQD94; zbBA?e;DR00MUMKaKw(V~vJ-u+7G6~$Q76cQeE7it{jxCLz;Ty1V)qN&t~a+o_%#OJ zfF8FH-Bz$&pvq<+K}WdgjVyQ6qKiS!m4Z5OsEHnW0Zxk1TYSyYs4HNqtNm zM4b*w4O^j%$fZG!JHnGe?7VNHgTk(&T4CK`<2P(}pdYkVLwp01PsuHySE23KUCYrv z;o#SPt`VnK;_}2g2w-+XDFd2)70LsTB_dU1m0c26eNt#0A=UE2Y9mqohPn}BPxQUI z!)$U=b=ps)v;jhMD!ZBu7Nn`;*H%UNW~i%GsKp_grA3-8aD%fq8bo5%jf6L(U4jk5 zLcg%OBx8VBZ*a`)K^M0ZS7dUkJ$28s+?@%-qSW6Idjb;EZ)N;9`5xXj@c2YE2wFh#?B%vl+%S&AE_8>*aU z;b%4kVtUT7oaAyL+>a+6Jv!A9J9Ng9%l3x;KgG%)<#P+}()L37Ml0&NfOy zUB6<)3Vm8m_i|a^bz^O^>L_C3x!=n-cC+3t34uT{ADrRQ^|-!uwg2(!D%%GnMT8`3 z{7GmqfL$O3vxj72=vpo#8)`ZOAv*9$D#VQdEl>@Gu$-`%uzJ@4&}&J{*R4=tZRjBE zC0>1)go!k0WiUOW2H2QT;$CVT0R2iJLx3VxxRv z;{YoP2I9@BG$vp=oqAZm<=(TQ2tJ;UwZY6g62K(Y@(;0q9o;T5Vjgpk*_!64H1 zNFbJ{aMF*H3DO_n8$B~jECtDDO7(_J`hd=@2FKJPd;^YkBzyrLV^V)X8~s`c+$m86*bj|9<+zqrz(CrF2OmWCIJ>r5GQj{(DoWCX(JN4)hmcOy1ExJMwo0-MU zDx&3XQev#)jB#J@4<{fg!Hr?Po3s_4h$s@F|H>>@0CMjF14)CEf;;e&6rP@PViXSS zXP~`NML^5K5Le4~yKx{DCQnZb^AbE$+$X#DyaMZA>xF$gH6O?pCe_!*HUq$vct~bC>SH9n7 z8>wtq4irMWMz1!oiYX?cOktXtCjtTsY7LG) z8O7#hF8e~^li)fu<+oW`&rlvN`Qwp32EJw7U$lMo((0tKa|Pt@l?#<8OKE z{WC^<_ET7WPW<>^#0E4gN0s%bPoF2`Cn^1ZAU6Jcz0dAlBa=S}yZ<6O1po10{-FS@ z{KKU}8RY{maDo^t7p3&Orhfx#L4ulO=NAP6VrJ&=#C{rdk-?(`FcysQEDrv6-=2s( zuOrdFRY+1uF5S+8`y{kY69=xDUTBa~F}_C%ikH$`j5Nj?Cp^s z)*=XFg(DiqEY_d|0XT5s7kP?l$4_s`!?;zLD^Ats<>e}5^VCG;1;zUT0BOL~+#8ah zu6Od)O~T-Xc^SF(eAN`aV=?Yp{CZOQx;X@(_gsWmSLWy_eDL=4O`=GP+HkJblD2-T z$3D|%HdxPI=yb zqzd_k61k!5X2YO)eiO+$(pFudz$wQ1>V8$}6pgXiWeQ%u!ltcxY1y6 zl>x^hg|({Ia795AZu1Zcqy>w9;ha`j*&grs%eiB`WT}O|E(WZ4Rkyen^`!9B?KsiV zU{;MxdX?1$lwj6jg0t3O~T?#?(yR^Ei|fu{yE?0i{?>(Uzj?j}PRyR_z85h5Fp{X1$)o8{n- zoIL)4nn(&jY_d`ScXb06J0?W-j&+ivdt!^@uG6JEA+5^*2SS+O_fsv2omJknGC(HA zWxwIb8=Xb}jE8p^MqTykOT>7Fv~6=Mf2YTFSrpb>gNF1few;hmL*Y2A`gRp_YhqK& zQRRz;Bh?Qr2R|y;lfdE{W#uEM?qkQd;MVC(3aOh^GU3RgC9{zSom82!?_=ispw;pj z^wmp2lqZ?^(dcTG6vX}IprT3V!8w`e$i)EWpV1}f4g9u)gH)SEIK34 zy|1mlYR(|Rn9g=4D*axL{_MT88~pqmx^^1uXtc-8dmsvJ~~R<>RX5T-9Ud%r2>@*^oBmU z+Uw7B>OTr=mS#rQj^c*@F03g?+aNO_c-2eSS&=uOXykY%*h-WPKtamzYXSSIP_Z|h zZrH6ymTxrfv(xbTWrL6r!@Yg+M$tdlmRl17CZ^3wWy^A#X8QQpJ7fDIds7lg!W~kJ zyr&ldmwPXeyxK`j?c^aEIMfq?szKQ$s^UN90JxX4@83rIhv*pa4` zjx`)IY?UBp6N93u9F=~MN-(gvW9MLPLWqhN?v-O4X{6lPbj8@_4|;&CeaYFMuse(1 zxD(0|Gk@vdb@PvzNt^iJ5ZGtm|Q?5?9*& z8Y7}=CUj7nPL?&~R~tRjhc}y~O)c?{);p{&1V8Jv>v`eX4-X!IPgD}$q$OIg#ULO1 z-e39t0+*#xV{_ootQ-1M0{{0OuKq#q|MxBUX`=Vf{QFNgV4*6c3-YH=6PVTBjqji@ zD4dn0pr9qDK@y6Bp!fmoxjT>~E2^1qEjKG+99RKjFGAg70dsl$oN>OmU|P&f5o|hr zfm!-|mi_9t#rZ29nk1US!eZXw^4H6Cm#z0MkFCs|?B8v#WM4GxRQu5il$Nn%whI8v zj4a2#L_#Wq6tJ>84MT$w2quB-<-2C6N6g#-Cd%B!dOTUGH~wMSPxR$_s8iwebWk^n z-)K$M0usQV`J^knNe3haI;aPzb}Lka9@1uX9`$4fu_-r|ZX8h2mEQyg>6AMPcdbyb zN?cV3Jt%3Puc^LmKJI{kR*9Kb0~=hoxQ(5QrIemT)x<{?7r9WVZCVvGMy1*hO$z-~ zkmEwT#O0Y|W8_2$BbR$YT?lIHBMLBx-Sl4^WJcwD2t-Rwnyu&Kw%?z{)oe;i)rJC= z1jd0B6?qL77BC`^80v|`F9dHCBt$#l)j zZ~svk>@p@pCLKI>=}LKdPQXD^xd*kr@j$$@1Usbo)0UM(uaM|as1K=P1%}hQXis08 zzAe+>Cc^)+Hzo5trRm}%a1qhKH_DJZsR{Yvj>y=Y(Q25C>vs4?k5ODvH2Or%Yi=R4 znVuxuX*J9U7tlGQ09iK2c)cZy+aRMJdRs|^JY1QgXdQ|pxh4zA+^$j1lp3N*QQRH; zL(y14KPvp2(RV*Gf?X=p)-5RrMl&PE!-mEdSZFhf61%Vy@{;PbLSU=7{R3n}wt>16 zX5@&Gg7v(lb@d=Vc`a#f+RHmGXH!B0vbsTu5HApA@oSv=db6J^T(OyHb<0W?R2^;m z$dL3|`eN>{j4Bf{(hBqOH$sG6HIC;&FZDO1lbBht4T@6?0x7jZYgonvY(Nz$_L&C1 zR6-=>*UK-By>8#jgHjDAPQ#%ufl*$9W2e+b9u^m8l=Z}1I>X_phZD|=j1TO5jMJJT#Ki314`icqnKKP&If?}_*J)`7HTEizTz$c6M4zTPi0m= z_&g!yWj?9C#=O)y5{&BcLIb6;o!MQu!TVVS5gbgj%*v{gH)|Z4Dc-K4z(3K5%}Z>P zjH8|d@FslBIF>U>Ny&Y^^O}}d>DlO38L!S1tIo&{C0KfokRBJHr?q+9m!qx>C!!=X zg^mscedLX~z-`xuq^z|xr_G@(@k?L3F;&sM>@$&MP;FMqlsq(quz+0?-!1%_ujNy1xiNZQRd;V))vb$G9CgNHbe5>a zr_#h)qp~i43P=&X)hrRsI?IO-MnBN0dYDaifb!e6BIh?);nwes?D$VU0HMgeWBZyA)>xNXRyVH!;v#%BED^m+z+iaRRxKa+;)#@9TSUtK^R!1!%n( z9SYdP{0gTc2@K7GMgtJ{AaBa9Q+uFk(+o)=O^uT!#HHqW#ULD8CO8Hz2}r`o+#_}yH7&l&2ed<>_5ox0dk)+cv%$JQ42g_nM ziAgIY2jmMs)KbKye+0L0UM$fHT1>~!=v+Ww#D8 z*?4p|d&=gwh;==HWjpE&7@^Z~T%)ymrvH|9PX@W**({U9vAA;h;@KmAM^N8AfqjG6 z{%%0{7P!TtI`Uy+_)3{|z%~MGQcp5v44qNQufTYqzmJjb0&X*-Ba&Hkm|KWBT;hf) zDVl+bA|I8^enxG}`uL4KK4gO=X?0xP{@cb*=_9rAeEoo|{bl7N?L(XH2N=6^FI-u_ zlYXZ-ck_sD)Z3&3;RZps%jopC(IM6IVZQj#ZiJhq^Wp9~gWp56?_8~I!O1t#jCe;9 z+*66J85B3Bl2hvud@9?s`)-PH!z)P9lv&iUzsXK$K18(GX3m$o`(8@Mml2}cBCi7T z@%D0U7c4AcM7@UkcS_6~0`tAAXC+RU{1eJ%Mbr%}^A;kU$21Hap1`oGM zLxw{r5@PO%fB$8g1U^h>`r>DiZTFd(O8p1L_Mfs*aqG`V@=u-FKRpkEVru`8765*W zPi2HgMkdnzz)Jq!meA*C{{yjh>sDtP@(E?DVzWNWxw%Q+R;gy8&l^DZ8B(knw%B#;w^hyZqMA%_Sk@tvcir<8Ehs@}VW|8r80 zVx$)h`6(|_`Q$4k{)6rNr$@w}cJZH7h2pq1G6M?Fltoc)q17B(UwH#c3Q8#v@iM|d zk$iy)5V3~S75nAF;)GfnyPxcj;p!eRVl-;?Yxxih6OBeg;_v!<9$pt7o>R5&dq+z) zU-0s^MNzSNS*<#b^v3(W;NdaMrE#LMYooHR9aGF$V4|IpYz4!`1q!bVaUxxb zJ*wKcXq+LY?e)}PUy42Z`csPKU4Jkpir`9CStl1eNSYeM2as)vGq4I>cmWMr8vUN0 zc(o_o3$LsOFIGqiPwbDE>TZm3Too?xM5Ibh>!gL0a@+M(WUcW6m|Nap`ga=%5XP5! zB=d}zF-)W~aqb%uNCH4}=R+qVaGYCr_RszBOavYhtjtk2rlLU8g2A(aez}JD=DzY( zW$K-{Al~JDpgtq*>zErlPk@@;MDlvjQ}Op+`~X3HLA--MPEVkS`;K*Fil#tZSz_%2 z^eQ9{>4arbwcLlQ_H7+33>pQqN}s{T`}Dz}$!?ydN*eu_NIH$)O6Jmcrr@WTVdMO} z37f^P{LL~ikyQKNdmN?##~@Z%&1S!d{8#M2I|wir+`YdeQ(b;ZuT)U6U%{%1HBHwh z6IM?CEEP;dkehAO&r4#8xEA!R)Lo7?-$DNjI4SP1?(5I9iueh*zo*vxb6Fr`^KY*x4Er})6Cf^ z5i1itoeq*OIrUq}jcsi9ilB5*T;FXgU~z zZl(<@pC(m)TKH3GLbvcsSkmm0mO`xGlC6LI1yV)7+_B+4V_3i^r2Z3PLeAdI#@@{F z-}0@B{~#u0>ZIFX8;Gq44$2SmEfq?sh+7Z_3K144{lio`xv*W%%w1?WWc+-3BD;an zW7uu?d!hv*GNl*e#85)VCtT0nejP@>{aR|i`hut59~|5Wb@|j1?qRe}XV4h|4#OmC zL^{a?(4XP{L2gyr^&{nKOsWH`h$r{ajV@QJvYapr$Cl=TLpEt57Kgs$T!a$Vpm0|x z5D_S_G@?O5=`F;>tJ08{cK;UH{(jS>=FjY}*YUuj@hPLS8b$5xsZ3f~#@S>f*Q%XXl5{6Kxd*>^S$*~s9T%`xlJBn z(i-yAd0XxrHry$r(!$d|3}V&jL0zf_xWnBrJl<>Uh!^g>`)*x#<%roG%u|>~9)kd_ zpz&#ZIl@{t2+8*fkGDt&t{r1Z@3)$QE@fgkt@lXBE%DXaOIxTc1}^<0!? znh;bZZqOT~5Yt9V_{x_{%k|3g4Ho;1KC)|$Yv2%YUf)l^b!skX5r5w`GWO(l@H7_r zxcRl_@sydC>w-BND3As-`?+~yirIc*xrC7@=)(!>p~2Rsm?VMkx- zgH6#^-s@k)GQ#dWPttosLnhq{1N1DRF-iGgE(&j0m@i{!F(5yQK zoedhPo92^Hw?C@bmKKbRu#I}?%0EY(4pZdyAa;`WCfufv^p_FU&0-0oqF3%QvY6z< zY7um@wn^|XYdPYwjWDh|(yhPzC9-)I6+HZrow_16vKOtu0qNo4g57~br(keeg z+x&L~uXOfC3t$>MO^Ad1gL&~n4KZ7SPr^co3IwUreT!Ywg=&q8LuQA}cCWezlo$?& z<2oOq9-GWOlmub7j){xw*J0<%#y7rhZ;)#QOPueETaZ`a>cQpF_Mb#n=oQFCKP8c{^6d`S~-YSo=Jou!?0b z8DOTYv{7-ZWjsNm*(aEfEk=UaQjADf>Z$3Rg25U!sgoIEDs@KcX?q=++I26MDWcir zHtWupuqGA_WL6w7jdxPcW5&DR(;)a7-Q&;T=MFLL>IAn$c(KIQ`bds!L20}tsNK5{ zHIU&SaPKHDVor4hnLmYBK@H7dOdY#@y$FlJl7E6CTkZi@PKpDsgd1Yg>XlM^Ti!D0 zw&+1Ekp>&(J|x+#+*$Ne5%4Dbv{=kyw|eDTZ+gdcy3LGcnn9Zd--rvMC9y%AXfnG7 z7|NYg_ywcRfA=WgUyNu5P!Vf*sEW(?yDrsO19o;et!LP&=N#iH@6@bQyRf^z{5d}p zPe35p{fug~pOE`cXj>&StA9(|{=aHq6+ukX)QxWroR}JepqCN?5>%KOYgRb_5_dSY z?LH!w3DOfd8-xl0`j3#N#icmQ9Q&pAhKIN7@kxu%`|a%kw?~n=oP5U{6@CVy2KNV9 z53S%qkc9F>T<)?ZE<*yRF*)XGti-{ZIAcuHGPS|lcCEWJ1e zHnIgK6cPk7NyRLw_t1PUi#bMAgtV*3XBnWhtQvamUdCjwN_-eWZ^@dE%zKb~XoiDl ztWs#V#!=o~?=~9Yn1qIe3H;0-CN6TwON*#s=p+5G=8(P8-yE=MOvGt2+=u>MUg?QZ`w9HRa0 zPOm@L4*#{NqNBZ;wTbfQ`v0s`l{EjD4)#&B;k;HS(TbAb@1JK0?Fk|0j(jjXC5#uR8VuINY!_US^06`|g8>ME-!=)O4 zA}&A022+3Q3vVvHkomCz%g7imPn?a_+*AC zuUYIa#%g$xRRycLQNP#NI9le2cW$FJz^Soi(51!Lt{v;^wDDA~qsi=SYh$mrq7uxQ z1Zy>BpK}@UeHNS5e7eE0nO{lFN{;DOkofvUHb|T6XvONfMN1%QMc97lP}PzBhDV?F z^#0Gh!LxhiZx>>c+?5(3XQJ}4RuV#{>VCNvTw-H9`KK*(>3DS)mb4swWGZ$Y{@M)A zW~ob+zQJ>~NaN1=Nqi2U`~-u%Jyw-9H{&4pYIFpLycBiXyN$|K4;I6vYPio?B+;2{X z26@MLyBqYF1pz=2iac~z)DrfA`*wg( zgxCLq$kF#2d?%84w8Z9&eJE#JTd7#A_|q)U{x4n!NY-Ys@g)kR& z1AXL;@%{MB5%NUHcauM`!X4Ga996&Gj*?Pp-C(1am+w!L%OVOoqUBjehd%NX^7jH) z%}WTt^e^bv*Z2M)t-WRw%kFuQktRl=w-306DfWL{>%XV<{ZEUUjE&(xskcG}X~}+i4DWiU%Cn-(HtF8@W-EXGyCRsk zp81q*!$)RlmV7JX$*;c?{Rv&K!JqO6*t9cq+TBK9XOF^3zh&E8eX;$r#>+laA%LlY z*TAz(xLrVv_>~n*(Rtc*VlE^qBFW5As)r}Gl-UdTK*l9v#({<3ls={uV=7MW7#nSf zL){K7IyK3eHYut#5z{XmNEI?NAxxj218PRdzEU3*x7IN)F++~#Qi(Kj4*rgLBEooG zi%vzb;0FpFl5q-|m2=y8o%;$~&f@dy#?Ygvc~NUi0_(<A?d`;En>ya*6-+BT#laHQjO z%xRmz_zAwF15eAjYeBR;*EJ=q``-Jni#D*>dw9^vy;yz2FOH>ZI4Tk7bxvFd(iNIo zdHYKv(INs`P1WZI`}+Cu{;wbIk37=G`qS3k#K``CKHfjVX^^ZmT<>=buWyBFwa!4S zq)1#Ufy&5`WF;p{qazL35$3z*XmHzdd5u4aZqVp#etqCgui^HfY#```!0G*HQ7fi6 z>r2A18;z>L5FT!cNDOz#e+X)BO^qg^No6D)s-26g8LN2M9hlhVzR^d%cb`5m=^7quAA>NoQp779Z8YokE< zF4*e7Tv~rrbR;cad7Gc2tuoBNS<(H=!T4u0@xR}cPXluwlmlFE(v^+W4H!sB>K0$- znJ?*+B#4B8zA*^+Y19d}7_+N-Oe@qI=Yc$Zxs~XPtK;U2KYwaSM&H$p5kSU3nWAcV zWUF|ro+W?47f!!U8P-Eerhk9(Wc)St>OFnr(Xn+<`8$IL{0GO!K@jxg>-85rrP+#I zf^XJC{kwKTgTtP3Hz7H$N`u2J+=l`1+1I=APYG&X8lR&n$b0wvckA`<1An;A4Np%) z`WJh;VoiSz;tTJj3z;?6qzfG$=f&x_gM}41fYe(RN3AYlFVq+F`-yhBUQ4h( zSC-Srqkq3BOhbjmc1EbutiFUEPF&f7s;x)d7Ozs#PHXm{S*o@wfmgO>`hbz@+O`oU zGP(b7{(%J=#Kmfhve z_@u@j*Qp$}owec0@-aqT-tYK^9nTTn(fdbrN7t@hRV#C?$_&T2)fQzUYvd}|hAfDoms6ral&Vo;AddmI zPU;Z<_DVs7%MoYNZ1tA_m?VS?!$DCp=bLzVMhBMfDjT_t@s77Wt;dd!-womlRtH9g zubf`tC28YkHWKV5a?fK&xGuE$5N~Z5JU(hdGRGAg5vNtkGg!)XpF0mtfx;)Tfq^m7 zv8Y=zVXT|2A=+$6diGGnOOR2n@loNGMCHvX3XkA09Q^G|3zYI-d!n$JNP~fk4_iZ( zNEcm)$@r-Oiq#x7qn&{J)pJ^+ilp|(R-?ZzxhlGBX??*^biNLFL9p%&OKDQDa5a-a zE&>qNh$zLN`stN_U5E*}hTiXUjH5ADi|xp6$Aqmr~<`z}@UGSru|6#}wiL$rRT>MRp1#_4J# zGnb7Rx2JqIv^q}KQXLvhf4#&LHyDByF;#cXfy%=#vcE7r$_Jr_QhlF1o!CE`!yQ12H!#h7Z7q+>H`0|G>=93BA)hQqTOY<-6#J z?JYO1Epgew^J?({?yVcTul%hTu5W*i^E+6H^E>K>C?b}w7}U5}R8<$$Xk~R+&}OE- z7-K>|#&}l8nBGIj#Dc0jiA~X@Lq|c%g&Vx@!maDx2EUWhjT|NwE^KR1m!X$TExNWG zn;fIS-v#weNBeD7D3kknyawBgZSPA5>8GSN-YQa?LPg8F$C=ygunY!@pOIb#Gfv8R zv0Y9MG8uF>R@U2X>XW2OZFDQz8_wKpf+(UQmbzG-NXwn#6(lWU{t7zEVcM55HrjFFssHDjB@&n2;P9b(u3o zCP-xG&&6H3+|LG@2>bIikdXr~QB zuTdLEUiGdq+46G24QvbnhA!_GsSCrDgXgX)_&T+^KU-5sQ>V!ST|H~wO5f`&O*Lw4 z7Lck;f@xD=zn{56og*~pZMT4^CsJzB$H0?^SgOrj5uBIh?+V&oU7tV8@L)FuIf)^} z7$eoH!W@}aBJaUdVTsoS+~lQdfOcMsz^)IOjGXIH<_mXL`U4lKFz6l5kQxe?qj|0! zUa#XonRlmOgZ2SRGDMzju0Vwo&pdii1Sipj<2_~pe@}t*ZH7n$BV?`i)IscqWCu(!aq5!^#>#7CxOR?m1G45kDjerS=WLj+xa>C`$w4^Vs4LQMoxdTJ?&KFV_PN7<{wt%18z~%G<&ecA$3jj!VZ%ePD zRb;ua`ZfjZyIq0#@&d#!?13?*eJI|RR3`Z8{l~*D@WY;dn0`;?5K>rP%p$uu zOjoHvI3x^jWe!(Z-Z)lyGKNLsHXWk9!U$PdsOC$~5W^lx9zbMHird-1TpR{^kRGf= z2&P0xQe<3N1^!^zj#V%tM+tI>$reA)K4yM$q;QTfgR-Jp8LMhkMfkY$1^*CU${A{4 z^`w-$AGr@xnaknVP<-Zu-4xOuR)0TS_YOK<=lk_}*y9UmPj;b*T%9$~6pIJA)U8#t zNMEIhU>X6sJFe%rZsx3^&ghxOcBNZQKuv!r`Mnc0 z*<5~DJoxto80FeM=offM&)}6ygf-(ez7#Vz5lVek|Gg=x@?>k~^`?w7i&hTAAscYAGO+Rw! zH4o&G+Y|WXm>r$|nyQ_#F7NXm@!iMVf7+`s^(NJHgLJcqpB7&hBQ%i#N=@RtOq^2s*!X#rvN}O3xo_VBH6W`|CE~(9e zMui2Meo-8_N+kh9(`z<(dV0>dPXC>jd3&3g`31!6mK*@8-G_2DHagK-Bhc4~5lMqb z5tur)pBKp)oc%5Dy1c_^b{ySgiP??XDoP5~)vK*{N`0S;sV=D8^qxFGAg8RGicO6g zTqLt_>^yL^`G`Ak%sNu)=`!klHLWg4d#E!7kp*t+fe6o!N#?3euGMrd>qva(l|Ow6 zHBL6*P`5~%qFjs>+<68zZBfyE6nL?mO#ePiWQmWtjW+YFcCM((+-Ch(h58{-gNPC= zF!u?qt2ZJUBbW=Khe`z!!`gH4^PW!TkSZG+pg>JB=cIrD-y!SdPkrwZ@KY8cZrP{= zfsGrtbN`E6Pd=>nQgiC~)}ekiuW&JL=u=H({{xnK-Nu!9Re`g^>`lVS()x{Bu%dfa zMOms}KBz@#!~HBQfhto_8k!aLV?SuDB6Wl~IEW5K1?2`XgsNpM-j$PlD?FqW$@sgU zV+*q@xLK|(w#>jY-cpl08aM9t?^8m=jKSVMGeV^@yB(Ua7(Tt9?I zUVKTnTM{+~BI2w!I48_5(jT+Nc9Dh`O5HxVSS}$C`22)E49X{xesnv|z8T~cw{>m^ zi7&rwVsXzp0na{!sB=b`0Vnb&N_woO7w+U@=-9`yx$DtOU3oes^c6F_D8`ksG1y2_ zSrer1rE2WwXtr}+WiO+&_KZLG9v<0K@&jfrLR_!)%CBr(^;t z(MKuKk{qgpil;Osnc!j7s%;l{GU1pxJa!|Rbv>IoRKci;cP5GursixRTd}yqSF3TL z(@?6@e*CF~oGt7sn^fN{QBCcaU|n?uHr!5!Zd5#x8h=!E-?ov((2jVD;UM!~p`Zfa zH1#3@4RfrOkyw52pXs{bDH@}7lst8>ejn9)RKq%@Cj99H5!pQ^D{43H`qh<7LbZ9c;f1 zo&M`?{}=yKrE2Ynq=w={hrqU*Zl+^Zpn|Kuh8HfuBTYulZH!{{&M|3bSqLzKoXm#8 zg9?Fn-mKsXTHhA0vOtxrQii25Mun|0MYE>5R8DjHSZ|$XJjDiA6?_JGidkoVz6>fE zPmq$->vYmnodBg|$ndP{nQJOGRBa;nIOv^wuCPL)N=<6NVzK^}yF6o3R#kca7_Mq( z)u1Dh1>VLo1Q*|KunJPw7s_cqE-PiSk3C?@&A(Q?vh_>oUFB)JuWaRrAYb6&k*s4Qk+FAsRCYnbYCe!3J}!4-$lh zJ8Rcxu}*3@b;#DaxO0SNTDi}}g4)nZz_4YS`1jos6@SkV_1okdHRUF(_mL2KYMT>m zM)|O=Y@f4R;T8l8RdZEUVKBOPJmjR>AjPp2Or|e8-1asr{Q7nvbamnlc4S#<+PBW! zqX65>xZ#5<@F7jXz$i-1qv4$G7de8=m}qxxQ|3%1RAuxc>{o#J9|oB}v2_-#72CLG zgP2y|s-2GeP>*&x-K!?00dJk_?;$ehgF{#C)>l1jlCtG$$QLSzKq3gC55T9Ee5&PG zTu}fcJ4Yv?>D!Vafb(K{=J!r z^z$iX5r+t8bO%sXYUV2fzrB9{ePI;V!s$c*g8uAsJc&W%=m+rF2kN~KBPQ;UOqMk2 z2*;{M;nX0l@_=>7%(|Xl()|&TgABEd2bW@5L5U_U-c11Qwu5}%M-qo4=smyf8e|j_ zCj0!ExPxf3CmpiP`Z@wa<22VUws*Clj>KLoJ|95mArYf9UyzcRWVsO0`rt#>3B*er zaE{y~VT#M9mOGn%ZqOiXg*UIBuINjWHn=g0mk>HavX2%{dVEO!$5~ig*77dd50YvC zkPnvBZR*NZH*m)i&@ejAfxf=1lLlyk}tH06!io`Bw20HF3NIN%Y&>hrvJG zW5L-+dKpK^8B*pwU@QUSFCA66n256y+Pj1E9NQ5pkYnU>Kc~rJ|KF!9xh96KF(*k? zy9^8M!b+FY9oLT1X?JB?CC{hhGw!il;~k!Bk07}YcksXK-sI3Wu`?CbGyyuvg6?5B zFH&a>H*o)zA^cDJ_~#9rjnAL#bh4DmdCzzH*w181pz#jnvJ?fc-hH3XKU8j+%v2Ty*Ov=z)iiLB@KGqj z8-)-BhYVy>b{7Q)4kZ1QP%j%JwTgs^gpr0J=pF4J?VaoeF<`6(kst$S`)|wezk1Pm zMVZ|C{qGUJx4r-9g^a#~rLn_5dSM;a_sx-h*Oiu8P$Cf_$q{H>4o#_$Anng)Y-m(y z$YfZh+*rWj_5KR8juSP2yO--u`};L=@A9*E)?v< zJ%T;kF;B@E_oO(`2lJzYym4anku6?`8`M9j(<(#W&l+3^RW|QR%#xEm#d(3&?Z2_5 z|LQZ77V!SzcYk%i16{O#jNHEm?%#ZH*207=HX{o7uAw+YkPzk3K8B%3f;hm5ZOiT~ z-$F^56#^QNh8?Q>3@|{YQ8xh=+Qf zDXPb`*;Ve61wOlGM@Lk^!AFZHtmyqz_GfVbfLz}$b&UeD1#_xXD#52X;Zl6$2eSk{ zhKf+ap9|;#vpm)3skEq9>xz!zxKmjxS&XQab6t$2hR#v5x~I3oSHuEmxsMRZiUsDa z%F|7pvIYK@o%Y*j<8@QEu1PrnONUYs8_etN*@aGR$)C2mjlX3pyv|>L*~QKu zY#$eVryngfiOqdqEV#eglX7|rI(V*SOcAzqv+pGMoY)!9eWhg=-aIHB3i998H;lfu z9Si);0U7IrhIfPoWdlxn9ml^=Ix0A}qz7hK)&81nW?)>sRbNT=;LXv-_GCheMh`D+Q#5v7D{1<&j` zzTohM%9tYR-_B@7wQ{QFG$_~ zlpE4_l^3XP|C(K@%7WdRezhA^FEHIcwYKZezZ$nQIotm&{QK8)K??f!><|_RXb&3* z2={+@F8(nUk^s|-r z>#EdeSX&ibaEblPu&dzM)HUGXU{Ly20{bWsh(!@?i4SwfTToyn}ao zmmi4%al_+Jw%{u{Ajk44-!tXeUE6o)IA#Q0lT?=frtIV?5%Rk2mT8B zN}l>MduiJ|K}|1eP;4I{J= zSRL^S)PCDa2hwgf898mXw_O>sLrGqd z_JHN!ZTlo;ABXn6adG$DIveP@5W+-|wv`t;m9}mNlh&Dr@kix})}5Z?-O zB_Vg~7X5pJVG7WTGSztFY z8LDoZN1%h_vj0KTqPB>M1pJ*g8gIHseptPGDv0xo&b}(xJR@M0dw>@%3+$awxaV{$ zLWd*eNBQ9&5d%>vVH_Xb5Z7U=++my9l+qgy<%b)eba#G$%;~!5ut4e8?tP}U&P%Xs zx$Pj`yQ2@U4ib@nmWfJj^s~x~h%>d$=y0vf=orA1lk8+admUYjd#vUP#(Fu4D3XI2S7 zu-O>ZYEOyMQd;tGlZraSrJ|2Nd-5cdOo^@F6u=~fNJLv}XF`zfQhIJ6XVWKRx_$^D z0)CzMi;_3H@D&-iK=s_FKea_iyB239|FdbWFaxWo7kM7aG__uBrk+SgJj$iGN_x#- z@(*%>yQh}W_^mVCcXfZ9%?r#qVQu(oZ){kMy;$^H>xk|%;6>N%Kn;*}=fqRbHEi3%&GnsD4+CG8TGUKKOUTXbX_8&pmb(l+G z^uw{io?OVxA7aneO@hk|s3xZ#weS=temS_CUEMtr%2cusg4fCBQroB>mtI776;@e$ zd3CnRTf`c~ISUoour=scLnIi_85FIP7+?-wrCF}G+Ai96cdX$w$_kZgrO$cvqczri!%Ok~tp%m#kMJ$68f+8)8 zFE{Nx3U`w*9IaT+0L8qO04OB?Nnvy9HE!g}I(4C1HIVd4jwe!ycG+K;$$+Ar^0=E+$N4BE7Sd@mtx2li9Qg?b;_LC0(SP&z>PC zKQzw^+kmN)yJQ~(zE#A7OBzWg)FE33(@^1MI8rAG2zWZiVV;nexIbk)n48w7rzfPd zhbXUSJP61{pZ!^b{3m|bFJ2tXmB|!|a=#?MKGVJdo;?`>tYxhI&v=)6mjY`%)18Z| zx?U(;<`L^5d!_{Vw4ow$2#gnuuSsZarI)Qy8B$j2A*!BH0#D`9{R{c(Q)kB}sS+V8vL2P-a4#~9ZP-fC1Z-6V~JeW)8OF4Dz zhe!zQW6yFrPTh&2l-@=JE$~WRVf(oDNPOF;ma?-0wAm#1wFiz~qH6*tr_g@zG3`CC zZ1R*V^q!PZLT&arW;c{rV3W&L-S$X---^LDR>OTYPamKJgKn&jQK}nBqsIQAj0fZ> zsMYIFMJIAXSPIU{Q!1FoF(TLXmX=0SA&tXv7Xl5i(q97rhiBl5L`YdlwmQoed;YfY zRGj3WuG6I0r(oSV^kd7%+239iXgeoPKWA<`FL2~0F1|WTvnh%Oq`_QBe4hqLmicBC+}%3v#2R>#oQuXkUd7-ni6)y%M={wdNC6$OYJb1}$3 z$@z%n_CGLT zJQ3eOeZV+VQ=4_4mqqR2j?hhMarL>l1F7-Yo>*fat3Z4E-N{;NufyDW<$Tv8_$}1h z`I_MKqb%zZ>}@?cqLjM^^eb6IW8jJ89pz@@or#BRGiFBt#J7b;FJ_;lvm1K$Wr4n+ z`-=z!eb!Fr<6a^hS`i^MILG5$m`I!?{ZL&C13L~bcsIN5xCC=;~_(i>c7@X!0c$N6g5`nA#F7TYBDXF-4@JcK3M=rfUEZ=z&f1*rvl0NMoXfaPMwUJn#99 zYpO63N4Yt_bVt%9q30r}FZaM?LyF+p(V_!#{Q;jJhffm`W2jsCcr3N58*uP_ukkPq%SQ}3XZrueQ z@xmFw*=|5p_Z>68cuaGQLivXXJko)0VSXG&gzid(?oV(8*C6zns+0DN=lUk=zdMh= zk`;LeVDOH}za|Sk0>xR7S6}yehD~Qxt|IB;KXb@rKgSs|Uktt7A~3?#f&2ajR{aI( z%yRRKY7wuPzDwSd+0>|Poe6U53!za0hihbJJ;X>GBIiJktZUn$dSXU)I^LV}U`5z; zZV-(N=7r7hFZX2Rbsdm*4i5i5+swiye!@L@D>7I_ae6rs$O|5_WKsU zo~3d=(HU(~S6>DfL|!#OFwc2u;W(=&Z%ZJ+p9@h~fMy7!4yP&~ky`}$DWb^?os9FP zy=dH4Ug1cI>?fwzK2E{*%1q?5uJ9k!qQD$?H6?RA;#yE;+^Bby0FBay{e1sP?lUJ7 zyE0H?<>fg<$C$FA^@7#SYv^vG?Q8g_;Wx{GTQ^^A2E^+XfA3o#UN@TUo>n?H9v|ST zz72Z4=gGCkObGrxmo$`f>9dy+=#5KW8f@_8sYbp72iU~Dn=SjXRx zh8d<$V7@jHIkY3Q|> z`1U3T#Ka~7W2n%JkpCl;a5@DWB-u`>kaREPa)cHUm6i$}8lA#T^i0an0>iGy26Sbr)@T zVM2*|5ne=4Q>MI`seJ`A0NPs38LqJm)g#$=jkQAxZsET&Sy z)ovW0(Q3G4tuk?L13&$n#4RvTAyNWDe=z1J5*;y(KDa)=BGU4Wd_VCdQ{)4XDF*)x zoi;rhXBJ?%96Kn~oD*@Yk~945G`xV-SS1256ly`yq;c33IO0aMM>#Ki5ETeB?^7wY zDv+$d@2{zkVPU$x%|ELgqJNjK!#LS2<56TGUl(3b0b9~&gXT8YKdtJ++)~$(Wtuwi z!Ty_zdl4&Al_*EC%EH7?zw9LU!3JGWCOc&O<|-C>z^suwdZ*iv)u=tlrOtS&)WSu^ zXYUl8msqnxqd7svslaC8&xj|=Qr;Tv#`4ck8pS%^$@yP+#UV0jFIKUO@bLJk^N|bY zX)eP}w@S6dNIa>zDuuY>y;A)BBoc}PkV>h=+Peo!pyKi5Eny=!gFSgO0Rw2^GfkDQ z1fi||6?FZRA`sg)fnIWf5OJ0Sf!ff!q=COuthk?05qp;N->TE3_(Z=Ty>xf~M*cPB zM{sk8A|%Py>tz)u3G{Ew+*N$129Mb!@RPh@hsG{3xw(P_5LzP%zCI%XtWmKF_mAzi zXic_@8gBlZ8sNXyCjo0wtH|%ip7}eYhWrouN=EqC|Kc=NuN{$9P`g-x7U3^t ze&y%PZh@3B`z6S|&~n|?b)D&W-SKSm`Fb*<3nXmM{2Oo38{?T6Q2;sq&uWAtU+3P4 zFp#yljQ~`FK`PQfBgS;ZoEfQo~0U4t~}7JuC`q2{G`MOjjuUV!fYBw7f9+ znI^%3GpZ)Z5*tuB-2;YVs8>|=MESXYU@FBkkX5P?~BcNJu%**LL^EuDbgZ6xnj~HT%8RD*hyNHz8~qSQc$X`ykbc(YK2E92w6cfy|^^k zkP>W#7B@l^9Sx00r<91u4AZ0(nX*;HFo)sUV&tdhuoMT$0ct1JK`DX*&Ii-C7*(W_ z+f+y+%+KmbCIcjw8hmlgdqA$rpXdjJu}~=d2bjO$&de2Sd^F>&;g(3zk)<#es`j)) zwTqSUh@q13F>1~c2XQJRo%1d`MHzH2VfnWn5M zT$vCH9fIKEryANw0Y_T0hlU&`Nl}&i+93K#U|X~lDz;>fYH>V{I&)8?4V_SZ_frGW!q=${UO)X3HPtLTu#DM%7QzvX09DOOVvhk}4Z`<~%z23;s*j8t^K?E4K3qU>h0;TiM>xIpbaL zULaGee%@|yNZBXlkZ)~#@-1+E^shiCf3)g)Zl(mQ;ch8!*RXVgoi>=G>`?Fx2!hdI zZjoU}QHeW$qw_+bvC)F|qAEgfY~SRZw#F2v>D@DF;B5+4wAf@oz9#RXU)b&NHnJCp z&QH~F#}Z56VdoiAAJ<}wSg`WWS3@ztEx@}qr0~W>+imxoA_vvBDF;m|h9Y2k)W>>4 zu^fqk63G*da=^wr;xOi5&w7?i_XL zlZ2`+EFAy&oT9s^l9MqNd_SN&$wVOa{X7dh;-fiyq zhA<}xJ`67S+@{HMxLziZ9i0I)4oI6*`jDReu6dhJ0vnX}4bPXRIMma^wSEVfS6L?; z3QoH;xHtj+91{$EcNw|P;rqp{?Yo@i3;;ncuFE2qpHF(AC2hX3LEga_{e2BOt;7Fa z3gAEUj!6Glyv>ZQzFpNF{~!6sA$Z23zu)J{$i6cc#Q)5)8XG#{o9;U_@!Bj3cN-Yjyr9|;2loIFAr27Hq%VY+ajko)057}V8Xw~saXbyw6} z0WE?=R7<|1ah!dED(7L1{LBAkD_eGk0kpy^)l+VV?|Ji@dxx{-B*nM;6-|KKL@3md z1&B4Y4uNM>nJD;{pb_CDj4F_kQ1$6&EG%K*9F?>XdH^9#aiDW95}8APJ;-Q;+rs{y z9|0Ux&KUw83bj5C(t!T$ORSqc0Olu%8%H3S(cn!e$GL(&uWHc9Z6HQAilx}#O`_Ya z6T%GWtrfxy*o_wuy?E(VS#hyZh%$2t=4%%32B~-li8471uPJ3aYoXD7c@N>s^HqWA z4_}F!v++VI!puzh;-sg_eA9KM)7?g<;x+ zKlr#K_$;+1Bmh|~lhU4a+{>JEeAOl;;>O;l`;*=jZ~f zkQesLm?izn4$O$HDdip=u5eE8*3*M<{=zl3&A#&>2$fX5lk4%trj|D z*O8iec%#&7T4}$&Mg!BEt$x-pGTGbHa?rbDm`9^K5!-@5Ylspyg2oJ=muhBKlCu-( z50SbxAB_xO7J{=EH^{az&>2G%MRumCx|#{Sts@0(2*Y5q3&zzpW7Lpms4z>qRHTi` zmA5Z>GgQf{(|AKhX%Z)tuVNyy;z>}q+W_JaS@TaC)DcfjFjoqG^}kj@!h=$=$ zh~P#6M1{jp2!s>02A2wChH+c9hha}iW{Lh1l1(!Fa8npog&D)>)xq3aB|Knz0KJKi zOqrwn-f+gPk6tHQl`hvvb(SBYIVbFX+9_vpQy4aGNS3GISgLcsRQ#iE0Xmj!XbRMH!D)U&a=PeTqpDsoPTSL~aJhzxgs|!skqf{})ud$HTT0gSUZlYQKk>U!ktI^ph z54iU}7nItJe{#C*+Rq)t+8(xN0H>-&qBSep8lDLY!B4C$W!NxDvx9@=ymaqkwyiy} z_yojmx>a`BiCDoYzMCrN1ekJMW(8b#0N!cdOD;oO94bS0KJEV6;rjDXPdX;w z7e+mCbAz+#GwW%BK-Zuua7|16d1Bs;@do~qV7ChDYVCs~M*rrdEdh%gnNzmh3>{jb zs9>QD5R<*GRCpO;vp@8iypCo{!?J#vxxuNJDp0EK-^cQ6sBWTYH3+ne^tE9RClNBlAb{hRoCbA)r<}Ai=Jz#+*EX_o zqif2njh&Fx>KQoAu@xHYQAP;b6ejo1TBZ|Q4NYztU2dCD|JXt)Er!h?)-n2O1dyf^ zp{dCr}utaQgzBuCLs)SWdj(gIKtod;dyo0WDv&u-}PHZ?XYzT4Ec#<5|m!w zd7ev=92w&h>P1;7ia-s?YvAt3+3tZNhwN>>=^r-ww3-x^ub4011OzPzBv6BIL93!qX+uu9}q#@(oS-# zodz!I=LydBdx!%z&x^8U%-{TB4OBgFEx(m2>ehY7*_6*LCXH#5?-7U`+qx&?vR%b*jwCC68f6_ia>$0 z(-*G$>3aL3LPE|&a~`zA_ftM(L0QoLoJKX7{pf_W3fYOBAbt%w!z=)Y&z@Sk z8gzp*+6sziXF%=m1?gc-4b!y~$9LT?(x$W%l~2^ZY(qpJnW^qQ(>)&1+klEKvSiDC zS@1mW#$*N=*Zb;S@=RDa`YYllf~$~y`L1bM5P9tGCm5o=W9C*a9NqxulFLLC+bmbg zIZvzi=Qb{b0-3M>X5jfRptR#xmb>)L5hQ$rC940go%0__|0tymS!4wi?S+k5CV?^s z%1|`nTy#u&0t&S@Fd|f9s4{r6g*^ax3G@uuS$ilm`i zDzD>{x@VR16W{&G^)o(@FpVg}5JgNKgck;E!4C<%GPFWs2%?4X4vZ7RyH<3;LG_)M z{vXslhm3uO@gu}=d-+D4rlP7;_NS{nv2dhy22|zZVVH1v>LN;$?r@)Vhj1ai_1z;h znfY}mNe0=NHHDp$X{*I_Qs>b|6wB)};FI1(lln$V>`(^k-t6`SSIkX=k@c-4NvNEb z)HM?IW!U)QG+KJa2?hC#BN0^QVD=j39v_dZrHu1w zAw&1DLh{;QgUjpv3;9@~l86>2mx$Anq(xRZ9ymK8FIpkh=UKL-57mV#O3R1%hYU4( z4SDtuta+$I{JvxbdSOh!CiQiC{f8L5lKkT2x1Lo$e~*!cYlrRir!8W+ygi;NaAU}Rzn0Ji znY|A(Mi(Dsj06w_sSr1alCIofLyAtc9C*db<%+KTXcg(;t7i)(T+v8g=JRlkZ_52z zZCX4Yupdxw5q|&^ax;%`;ugskFQM+=YZm)hFN)U9wRzrSf9WL$eUC!oCaIG`YI8)g zmm|88fbopVD&T@ft!Ay>E|2AnYB?$G$5UA?Xg53%`}}X}$N!ofEba6=pWi~+=-&#+ z|L-?1Vk-pux>fpbbg`lK=IgDTo=aBjd)FWUKH0P$yOa35?l2k~Pgw1Pgf(&Kg zShRP7EE(IcAG2ZuT)ns78aj18(BCvqQ%sIVK9y(P#(b++UbU~qPC7AhdOA#dX74y| zKO6r&+2Q{Ji5+l3k)RR1QAObe;Phf_N9&d0vlpBWaXZ*^`fDn{=qY@Dfui zE1i{8QZR3JXQh@D)WCUWZm9mC^=^3t`txFdE#4KRrDnE5tR38?S@M-xYlMDBIXj9L z<#MHDwLVX$eQX6(0bQl2jfmyx(Yys=PtB6GejG|UyEWL#Jg}N(b5TZ8(KM<#=|dVv z@@BM*4er=m5L{EcKO%ds`Gx3J^>S%)V|!#NM1OHduSq+kX!(`I@c>dt6Y;in<%atD zIW4wO!#4x>wx7Dv6Ppd`wu;0CCf2H1x)j5Xhh|Ysi?{*R7*&#$r$#~h5#9r z`~&e8F6vaZ$~+zKmraI#FiU~s}^QDlgVO<8OBjmb!<@8&N|#i*W=2pY1( z@ZOKZ!sE_x+DYox_f1htsx<$}mtx=wVT}%&GWbcyD zciuPde)3aaf4|2|=EzL_-;t;7TGQDC$y1YA0w1+tnfW5-G?AJZLfOS?tvbEbfTAeY z&0gG*(Mst1$k8=aE$2yEh9{~ey18YRYECt2!{G+?BiYcIvE;h>O(hu9Y@w`)OuZY( zU_9y=Z+^%SC85YF4u4n+E3Ws%iv4?XUY|&XhQcSi?7mE~kHrGAjj|u{81S3hMSm2J zfcIZQ!QMFpq+S~fcf&kB+KtpTG@GRqMcd{c25 z^$U<`XToC}Zl9>2eAhDNZ>`0JPqqqqL{2`HHVU9(aMBQ0Y8o#JhQA zvgpjp8@CzHG$>5C2XpQwIiY^~U$N{B_7aaLP;N+nEfzUAv|OuupJ|0SM~V*M|lqLG=PKmloJp}b~3KUE|m z>q7rzp*kKFvw`}+Ahh*$IwRy{vJR&PN)bWL4GV>eWy~xEF;k~u*X~-|dCaWaA1`@0 zGi_s_9&1TX?tQ-3T_=0Kwofe?H#0rHpRhoTZWw_SlVEv2jwR^gwTRlJ@9@S@<10w0 z#z4l1B;tjMCE^MtlEt0kjpHpzFym>$1P$UVW3|ZZ#2rZ3B(e{|$1H$s!1Iz^TEMS= z?)?CioQcwUiSIpXyo4UNfY@;Z;s4x)3kc3MIkPZPZE$EepNx!Q&@;un#Ab`itipP# zOdqrSjN*Ow z8*w+EGo?S{1pi~dAUWcYru^(+B{5N%>uM#`RBgWOw$4DiB@SQ4Sryag!&?G4&u6oc z_l_5IG}1J$E37fZb4@xjw4olxe6hCZC%BZy)m61Et&6lMG+ba)f1ax=Z)js--9R*& z)l^8ZrcQvKSkIr@ST2;Nu2^5yZ3_G>q5(hQd2)4m$Wu_eb;#@+3-{1_-?a%!(cde#TKc?DJ-_F@PnZLw1B8W7D{_BJZ>Kk7?T9%E?u4v-ERdNtXpQA== zXz7k>+BsEfoVKir9aZecwn1YP)8{81vS1A_o8fM$z+Hem^3!$RBho@>7Hs5~9mrL1 zF8)=Vm(U&Mn0NdXg0#GHeyOlRSU!>nX}GMSa&f7&LRvnPh;g{A0*l}rs0cEOjQsb7 zc{mk{3PriRa$YG&28_m_66}!@E}xx=XQyg{su>l#@n7I47(o=aBIyS=vcV^FaZI3&0h;u_a&nWIgm()r2F$hi?XX zo(+N~fu;Er-gGIa7a|*PAC|80;&~7$I516~9MTTK`FOYqL0gtA{z`PwKJC?g)AuQ9 zC>WiQo`G3Nv0D$Ao~iPiw@TEJP}=j1_cz#UZk><0kzTypM4LsdZj4~OMR`WxZU2_r zafIh>MMmukH+$f6AM|i;cY7(uJZ0&EgxL|^w>{;uDU4%fCGExrpe4-=h`V+6+Tw_3 z3tE#T&!t34A%0itrXOnO9K>a1PhqI^@18)apjbV zH69PlWqF6phriFQq&Gx^1JCWUV8L8jFvzoU{Is`W*n^O&f|wYks}-NFT@2g68A!hk z*N@ZEgABJm0I=)Zh$E)5Js55Y9>VElj%o7IE-1&M~tChCSx#WjceH z@Ig$L>gXln9#EvWxu;q#MI9jO=$$8MmNAGs21Z-^{t=M~(m;_NWSV+tny7pgaJhs> zpomKlsRXlHfAAIFUZx|631CDtPjmp%bS+E7MrRnSCF(%DonMiNRY$Z4HlChDYrlhh zV{1_D<~A}7Bsw4)x$0`){L1||=^n@g;oL4CmX*hE<8{R30U{cB7e5jE;3IeY4EVa^ zjBtGgEj@QZt2bIS?-O9OA=G4ak%()=Wc(-4h<%U=sr)55xAYv|-u%|yyLz)5Xsq{h zV6=DqQX_)Icgg+6?UadD~PM+>&PVRXw z`hEYI_vF=JU5WWVu(0rLuT1ejhHd{4JpRAqwxkqyFYF=I5nn@zWE$IjVg)+QFvorI z=&S*_LkGLd)Ya9xuz;wnfXb?-Dgr6d_+Qcxrmd0^^d2Oec?8k&NEG29q&U`jo3ws- z0)C3?isQc4{D1IiAJ{9JYEenGefcWVwp#AD-@4sxO;l^VZWkeeP+vB~Xf9h0uw^Uz z*#EpFBkv5mX4dYr{p_a3?2?=JQK!yk{Z)?INgHVB#zp? z7$n>zAL4H`Ou4*nF$6rpui6J)8oFqazWw~us`>=KX5V#*-+Vx}#&0k}ThDJb!nVdw zHHBAuAjatiBUbqQ0f&=hgXg*nN$wL#o^?ZT)mx`UVyDF>$9dCu>AT;{tauP;m7O%@ zSnsaGM!$Ni!hayOOJ zKCH7`MtHa?nP82H^7cyc)14z2b1lg210g!vNYaP{2q0oXHJ-g? zB5m55c^^$Q-+tu$q+kVZp=ia_oc-cStWC-LQpr!JMV^aGS{~8nQeUEjsU)_Q2Hunq zV~RghsxR%po9i4U+J=XaaLhCN;zSPhB}T~aQB?#D^g?^0Cd%w5$LDQ(2OggAYhzL~ z^3O+yDw{2$7~sxC+wk_wJb9Oq24vLUc=9d{YO1ZRt=TYM5hlv$AE`{ck|wr5^__k9 z8;jEnHhVIyS^8?&?VC4oM3rKRQp0nLD4M36Sv3wuGR83+h9F4_bEEgQ-wg|%4M*$K z+vYzriF63+^KV@p35p36Hyun%E!u+^)_bbC8p1=PjwMUpOEy@ zNs^?yfIHRtp?v(O;G3$mM3w3tKg?1dA4>f;#Bcf!Xq9QwsZ_{0nwjp%dnW zv45Gi zgYM=~kYQ*Tw3?89@hC+YKBYa&5x3pZ!*5VxY7l&nwo$U6MB(Cbvr{D5vaTbF<%(A0 z+l{P*1=N+7b{T;f3Bi74J`SuLCwZE$yo;u~`XoCv3d?B`2Q&Qww-iv>;bq?vzZ~)G zJFFxmZMB(FASNrelCr*oVq^>VP+N=ocr+tFV8NYZ4zr7}qsb zZwE%^s)?@_Wkk_}W>rb8suKQLdqOu{s-Am%OK5&Ghq`u<9x637-%h0`yw_?q%CA{2 z$c?BllDb_}W{Ehy>FokLY4;MR=o>Z<3I^;Wp3uBUn+q4%*@xmU)M5R|lPn-}j>KO) zgRPNQ)q@OlB$35#w}77a9qOm@h-WyKW$%45upG@N28%_JWDaQv{Hio86?u{=2Ql$TOTUQt;)}=Td~^R@Y$7z=4E8n37zynOZ?XN7d%&i`ln!2 z2PCXkAF3fe%n4z5K>Vtz4Af?|a(cQgr7Lhtt*;0liTEHvg$JT_K5%1S3+>q!YqCjO z{DS4uDt4;It(NrCO$tNpRY%;{s-$h7{uD-z&kWS2ZrIv&@YsTCON2cUZ^$>aKoD58 z`Ru5+`-n12^4tpbUYZ7ry55|R;0){;*-+7Jgc(Q!DBdF}lu7orPG>-l>tu)QEa|;B zH+o$a?(=lFncw@^SuGJ7RD|HE;5Lo%F%^i+oAx>7lE#LL;t039oElq5DqaO!Bntv( z0ckvDO*wY_6=VgscC0-`q+F{=!JY*>w~rE{Yie{um!G_o6Dm5#nxRA;nR{a8xze^W zBdb40XYV$?e4J;Su9Og<9F>{MbJo=)c@ z)$jV*+s_T~19gZ8KKpNKyl%u2Luqje_TO7IqPn0Y(%E~%5@X_UK+t#SIN!U>p3|=N zl$04p@ZvOz4;A0fLy<9zlD*UBp)G56psg&S@<(bOI>Z853gw)Lw^l7P7HP^^vIYM2 z5;AX8;Ds^{d*_I^0}j%PNw({RiU_tjGbRwLLULJzipaK|NPz=+*|YC$uqIhu)J$v2{#Dq=G#ebV9|Jj&D##e-@#RuC}#<`c>Mh~MC=X(y) zv<1fQ$vyJNBsTHS^%BCtH;KzdK+2uWNk4Qi!Wiqk)Wm?_rp?fPHa0JWL%O{tG%pBz zH!m?pC6%@i1=MBHKsL6c%=u~V01 z<7R%->$6Du(h#^2z~hn)IFJ1OS>$!Z5xpO;Hh-QX_y9@yegufN)`69p&&ndE zc!#*G+~FZW5{5!7V7tg3SP{NbG?AE(e?V4ygvTfFn%(NzC3%NlB$y4Q_0;tIfeVvm z>c&P1kW}G8?MiwOmQqydlHAm>6_N&RwbqQF$1_umzCS| ztoWt8tphzm@pgwj_dDeTpmYoH zF2|=yTTn}OL+7z+7>aS^K;Sh#i5Cw+;~TsnLcjoXl948~*4vzq4rWOR>saeZ{<M=L54UL+<)M9Lq2aN0UGc zLZX?COl2lj?h=}8aPgbp^Z28ZX-rtt$_bjQbIaR%9i>m-CY1n{XotObkKng^9cdZa zTTgjoq$Ghzx!sF)7s51)o{$;Jvf?!69vOr}Uanc$@sO_|VB%g}w$xDt?$js^+2 zbOiynD`qZ6I25O>Xk4t>BgRa%9)NBvX6jVP-yv>g0Wsedkb$)SA%cM{hTe*PE|u2b zRHu?hLup`eFQl|BxEeLCI|w254^>|duvO$g&P z`FpVXIvNUxBaG4#MICC@)E+#;shhrN7=Bqg@)!YfmVLe)?Ocw5KhH6guRsdgfkoS*4fX6y%GkRZ12s^rR12n56Qa z)tD3829RM4q7|ti$4&My)Af_D9~tHHRK}^PKEX^w;CK&4 z`u?Z;1G?Mjl^@Af#XOY_Jm|1ysJ-s@@)=n-74|eyGs_X7P#ZHE)!DqbP@Jahp3>MQrE}(aBxC* z$7O(5P#REN)_v|njaSPoH!?-lF<9Sx5c0raw!PqhwWdp(-FVzFz%b@}sG8w;A^bbZ z;k|oWZKzt_p%&3zwC_YzUIqbQ9o+FY30>{rvEK&n4|CfE?N)-@g_;cupKapW4gp_N ztV&4q(gP2Nx#u6%ljF@Zb#G?4+705o#j<+68%jyI15cm36+dsD~N4)4T+woByX z1H<)?RJu2p;fBnoGbHwhU#+BFe{~3=Z4)`s#oI)5wQIqC3;gKYOaB3QukjX9?m~mt z;Cpa~l=X#TSvpKYv4dCr-v0?X9ljb7X(*~n|mULtLRcuOa*!fDHzd{~7;4Kg) zV4I#U2_-W-A07uu_he~^OS}r&@6pBClXkk*Xo}}i0o{eig?xE@iGEa4Laj)qP8d?P z7eyJU=o23wa>==l^X>?NC|k0*5s+ACs++9y!9oS+TGe&JY$cnT9x&J_x!;%THC7DG zDEqo*sE=A-S6Qv{uql9%(tDyxg0yh((Kh_Ek&Q2(`i#X28y9jygx^B=;eopgyJ663 z6u-+!Y(iSblXbfSKTgJZ#CBS!*JeMrYHJV8hf*_zxcEK%xt_!7n_vjSet*+i^EZ~$ zoW6pKF>$jPwk`=(k}Ko27^7Pcx0P4Gze@;rIxxLiXXc2+rvf!Tbb*_dGE*yKi+|=Z zwIIOj#IW3lLv=7>!N{v%3&I*O!{VxS(K;aePSG6bqE>c+;kI`t(1O#8QkVlu zLHAojtzfTXYiDd{Z>RsmUf;fLmp3;Zl||>SkxHxzG*?NMZpb^*XQhdwgBIwL?LZ-@ zEbkF&d2jC0FJCTXfe898E{s8W3VtcKY#TW^gzhSN0@G-})7Zj8yDUZ`T149s(&cY}@Z)5;C1kvVozE{^>j3BUOao`S_>zEV7Wf`g%5&HVs?$dXwGWN_BuvoLMVFM zlnEKCCc>;ov(B#D6pe%8-j_|CM4j}zvo{uuz@oEvTk*s?5|i-CaX!pf9k-rsh1=aU z!lEAr(SL7%VD@eFZ!@CoT6G|jad-CBx~PClKu(oJpFq=y{V=so_*Cv>&2cE4ka5pVyhDCEu0!5&zPSiy%PXa@rL~_deA_}( zv*4MBJMcb+GA^jes)O)$lQ~@WI1G3DN){L0!AcK0ynT|HEtaa^fW1_hVzPeZ@c2`r zs1BJAcrv^_IVL-!td1y;{VNr2U4OLD0dzz<&yW|YmbJ}}EV$J^W(U;^bXB7XVKeh>C$S{9$x*Ptufk_>Ph4Iq zh)wQ|)8ZF3!}1vKCa@ymg*dA_rF(f$`1WT7d(#aM*%>gQONfkVnR6FC#@*qBDNtpS zt(I-$2Jc1F5WJoVkC0 zj*F;e#a^^N;1{zBM<9ml`)-e3F(%mZenLv@u>xfrA#B~Uj@ILbT7f-zcWZiX(}0~} zuPoEdMtROh7vh2FtmLFbR`4nm>Z4$KVOg#?9A*Ov8j3h?lP-K!jXl}3Bt31MJfk^O z9zq3N&|C3o`E|$#Io4!1MU@D5*-VkXM5S0Ygr*~E;7iGM$3MHmen!f|pD;Fp6qm^| z$w1W1_(g^GbYSol!$K&U?fZ?WHg~;g;cw}ZvANal^bCeT`0Txw&6SscD=h}_tn2oL z<$r$t&v0WuOw_IUt0?Zf0O-?53nIc)O*XY z105PR7ChyR^`CzV%ref3XBlNyU>axMBJNw2{9^lE@;m?NhLeRx$QLyWTTI`zv%|AS z-qejF=dVsLPmlJVyMqq}l?eC?u{Du zz0exHMw%5Ij9Aq@t*?%BLRWi_4)~wMk`(q80B*FticBRK=Pyq9p+@AOdO06+TmHw5 zHhVl^zGn9-%i?k++q)+yT)H6J+t;z@eb< zosXGtdJRd{b9B^MW8dJ^5FvZdc1I_5R^$hFo#uc&I@Y zjO16T$_VJLwu}*qLUTWDH917`RxMsb%8rn4u&XY#hFS~v&Gd#2d@N6wC?hdN`@YF2 z)4@z!O3jre8p$_aiP~fpS>XdKopzi#IVhBlKlF)aov?rqJ|pGVa1^F_i-ev+Z%mIj z-kT#(B&1R+)KpJYB8^^Ey;IX3^xB@Xo5?fmWZywF6!+*Ulk9caLGoq78Ojy0&z|;_ zf~nz1k30A5SKGGUq8()hav(-F|`6Yv-D082aL8afmM0@7?7r|0QnnbG8NV2Z5A?H~iW1}K1n zmVM|s_c5TT!_dc$M(dJ5>f4T%(kv)}((%Q%6fE7CcKG{RI28Nl`E)~;xs!)q>Otcb zlPdlBoE(#b`KW<4A_4{PbD~PTlL>2vBNy5@t%!mW&GcWH$w|4+o=QfSBgbF7mtU<` zjCn=fJE#@|6{Aly+teI6M^Z}rxk~V3m0|!?%rgC8NE6#t1(Is)0#Q4)dIZvnS4yR+%=x7c}BDH_sGiw72ysV(3#dpM=xsmO`XTku*^v%dyUIJI_D~ z;elr>__i58QO4{?_mPG5y`U@wj^V`t=*rFZ^iAnN=q~P(!T>KV9525u%B@|FF^$vG z&4f67H1lUwE5ff{sZMRU`{>*4pf288h;dCqaZJ1~(FEp2th{~V{QA7)JA7ovksnlg-eXOBh(+<9G?d=X z?||OrAPI-G>w-`C7%l4CCsF0YHfLHSTJ$MK#D>S_8>q8t1_O$ZYQ9epx=$bQzJA%= z()YC`FyG1B&F)OZ`wB-oc*Y!CiJHd)mr?g2kbW#`MWL z)5J{7kaJ%ieD7xEd*7m-IKM+W=XB7~U5%l<&%Jd2Ra2VlR>T@sWjfi@G|MNs2I2yH z4r%byLrAzoY|zJpr@krszRe%BW{vZgf-E=5us5Z?evw7BT1laKr?vhO_iM+uT&}Dm z?+XE_xpv%IJS61Z49n~9b|CU&aluYXyZbI7stB44ED=h)(R3Vhvdz8DpW8&w0X*y0A+BNiHa4&`xG z)BJoplWo_0cg9VI<+6=zm+>nvjK*2qgeK{HVB;g>6 zF6WFMNk}7Z-H^ZU@QW>iEvQeIzazt+#5XLWx8(iwm+`0VVP?~vb|0qVr=)gVE}p4d zD~iy)S1!w)hN-VBnjVG4F`cg2EAMfBX>5BXWnoLykiE*zNx7H*$Ye&gpYXo+3k7sHp=$~ zQ-s)8W@M`@=I{f^uqCZ0C>LrKjcf|tX9hV`5#krl_2S>&d6()$fA*{{wB>CmKFq8w z_eOV@bSBX&>!Y`NTkq{wC~73waQy_=(Id&*;#hQ3AP0-+J5$~VMN*=4@MPF6OnHsj z8gb4NmZJSlj&C!Z=M+u*>z!55;SX5}J3{ zm8p)h*A9EmUELUZ!}82Wq{s*zZG*|Vx$G z>=N!f#sfH`Trk)X^5h)Qdzy?#?dS*O=TF6!AU)K}P<1j~_=pCRyh5A#h&dJ|hJ>JF>k&G%m=G;2B6L=2ceZkj*Ez0MRlH3C@e1${T!>*$!VX(0 z;3Yf#Ov_eKRNLAgiqUR?o-0JTBk~fs`m>%(6W|cxUuHyyHp+&yWk_}VSEipmpy`Ih z)2>CbPv_^%5;gynts0a$DC3ZN)p&aO5%2aei^~1tH3n>EMSeNqxG~IrIJVs!WObR@+%vYELv(;;l+s64r3=@6r#DWK5!MKvw@Sk1PlhG*~(l+#8CJvzXT z)AK`u37|yB(6k4a664()-LJG4!E!<4LbxO$Y^3t!YvG+5NPGJ!Z_OeQTzIS$*_F`` zljpzItWSPxVinBV+DXBwU1Ync#)Q>sKnjrMphFhno6Fwu9={KX0p-Qc? zn;%M9rYkC|uQVr@bu*R%@Hf2^Kq>E=*-@&g8gZe=VnYpscIRw;RgO8OV)###8TZ0??h+8_10SQB|;)Ao_j^g7PXKYH8Z;Oti$>wT% zC|QPaHVT}h1;HF<`QK+ScD8(?LS%tttEc7wyS~x`3!^HzJ#F`_O!5n2*O_3r*{3;7 zFDCeX!~iHwi5EP6W0rcjE5Qs&WR4-n^QL*SFOMbX@g*2fWmajI;oc$+JN8;nPu|>Y zT-#rhSObDgKrtGC%`5S*n~fj#7kGkwQ!8G{%>r?upKxo;k_I1WS1r&q=lZw7@Z>_O z(?PPJs6W7~*;22`Hl7x0kw8F3zP!gPo|KtR!*j~WM*u6q8v~c z9~cLCMcgR8CBBcQ?>kRlVPDC!k!JD~C2>|DV0qSd%-+IhR_bZ924#+q9(KjjFk~YI zQ1-21JW(mIA?Od4CoRw;e3Yf=gOIbxK6M)?FX+UrCn>D?%GSM_59#bx{lgo3DTHwebq~MPEtmC^k*&u z;^V$nV3;!S0X)MP9{q`6PU_y$vpR85TTvv&YI?z81ofYXK5R-BwRknjMzqp*TD@&0 zMbSV{@B4%dVg@o+_#V`@u`2mmo|7Tg?4djEeiaot)y8|icSsf%e5O%$-yG}>8}av% zlZ#Dt$w`LkFD-}|5D$673D1tHDq}^{Cw07*6~|tlqx~HIcej+7zxMKtgi7EyHlx@2 zVgAudG!sb9Bm%f*U1t=$4mI70L3^t3l(0k^OYxr6Z0F?!5~)%fXWbxBXO@*!yEEsoJ$xV}81) zK_OL~xJT)5snOjloGJVK-H|pc6ga6zACGuWtu%3;?=J^B&iK(S^>U^hq+>r1H+>#G zKIy%^!{g>ASnM|HXSt-t;VAvJnI?6xcLV0oQI58SxFFVNma6f>Y3k zRW?Yws-ehKMI_YM_m#svKbI*V*O56{JX7bZPqnr!y=m5mW_EIFgU-TFAx{tM+FWNpX}x)<-78l67@vbs zMc!jan%!HwqQ)Uf%-+h`IUs~&_{uROwjM5F`s(Xg>kCR_l8RN!>`^7m^-omZDO4a5 zr(Amz@&MZuR%HY(<|&NP9)w6ZZUJrid2w^5%s0OGL-rjdHa5Wz~2nWe)z6T zkus+AotzCMJo`hI_zw$qlnYqO9o526i85C8La^aRGmI6=9h`HtMpK;GFk1{2#Es_U zGI@k4nQy^ipxZOvmJ9|MthC2?_1AkYF_?3G1NT{eeV^$u>WT;3Y`qwn40Ci}!U1v9 zCSB%2J2Oe%h`Y=4SR`x=;G`@yqEdJ;@cV^dc~f4y*t2n=rJ$U5$+KJ`LPc$6Potw0 z#U$x#sYMa?S~bx$P(YXoy1lTU z>DK7Ws5Hh{oCj#LrK&i$VoEM+V-e4$j*tR^5i7uJZ!i z%^(dYQH>M&n1$oK&V#gYLY5^NGwZ&gP_d6M!Bk-5*k62%b(Ha6V6#GLVYGTYxpp-9(9H`T z_7b&)Y2W3ttrahvexJ;YY2VQ462g7_fDPQen-|vAKk^~ZyJ~A8(nnQby{(jEOjV@a z;l5`z0nm>VuTDzCmQ!Ecuf`hn{`C0~9Ag^>a{;jL=LQ)t7WwoH>J?>iKTvu^qx$&e ztdDtl18Yc@cti-))*Z2@3DLITe zt>zSmY&lD2UpbVU+Tt|YlP--$@2tdYNgBp1%ykeZp1pF_CrCw0B7i-7zf=9$fe}jD zI5i;eLB9EA&x`xR5C`mz-62U^qI@kb>;)WB&7huK*2m||Z3GY7UqN_+5|+x&zSGb6 zH8>v?4>?6?dhA3YhRKv7jA)IsRDvQo4cV3zRG$LD%ix&( zOi+W{aOxpnSvvKjKi&A)1i84-Gs1u#UN6_j={5b9e8tDcVj?(w8JkjOBa5v_p zK%C$T+UR98vl~l4+T-;D+^jUgwsQ3QW^KyIT(8aEx+S01>MDaTW|WW}vr>vMY#1a? ztWU36voEx3FRl&o3O4H~_#ZDM$n`aO9$L`IyWrS%W@46+3q@WM?Tx$jUZ@WmUxa0>rk7-isUmGGHX*Ay7vybux!(GPp z$DXVT$NCOkOWWf6qSYQ3MqRvv$|C|3m(I+35@PPHDsk6jkI8``l7&gSNvkwvd1C%k zZ<|l)=PpoZJcUb@_Pi}zR9+(|&LMi3?oVyn%7 zQ7W+^+8|zA4mLTKqr7;JSxO+0{iG~^%>bx$q)4A=iU_@?*5rX6*xWK)EgjoVXFqOB zcS$O~qeV-O$H#gwZT@MreXZ_7+1FU!b)EKef=RrvwZ-Yw9}_-uc^EJONuL?QvQseB zIlL)hkz(6n6NzAbbt}IC5wzptc*GJ@mWx)PKk#oa1~d9(hSqFQ5jPd$NkNOTowP%X zCEJ*wMBv&i7F$`%J4{T73V$Asq>>&rF=mn);CiN^t@vgX9Y|WVOOxW?i&eGj z>c>D7J7Vk*I7$oyS$b-WjJLBs5%KZLP4CBC--Lf< z40rJ{Y05{QPg^dD2ycj{q$8R@$e>eYS&y<*EXxjm&do4WGV-zx$sTi=eUrQ;g-pJ& zF=YEBsWA_?DO_c#k>jg&Hwq?Ce`$*PG`fw1<@&LKM^840Z(JnZ(sr}=Q%`N5fdUIc zc=f3CC=~VNB??PhF?Ba>ysm@N8upOhk=laN5uCD-w+#mx6^rP6B@D3wpK~j@ClR@^ zb@1HjCbMyYtl(Gyg>4fw9EAm1?r8(8p9FPcY?=4d{C(U#t+oD>Y4l=*#U1ey(jM(j zhk&||%jPW#Ph;t0y8;a(-&XlySoy1EGq{@p;l&EoAFolXzXd~9bwns`icVi<7)uf^ z+Cgjy7)-0)pspN6x6I>ND~py~M$w#3Hj2xm+#~va<38(p**Az4wR!>U75xFPe5U`s z?EAUk`BIMoOs_Ttzst=M3`oXr9oO;ZkGf1^c9xCLtnwGk7q|>^oGfU4Ui^M}K=zvCGOU$) zm!dYc6w4Hj)MlG)~Np&;qgm8||`UBD+@ZMtC z%2u=V?3VGWYQyQ7>IKgI<5!7V%OnLeLrvAD(2a0MYM(L9o8-pDQ%qU%>)^-2$r9o| zD;8u^S-s^wjn}V*dqpGaR~4{uEE!9rT;#j924R!81pSVGMBiicq1cl+O4Y{|Q^k9U zyKsgi^a@t6^Rzp~ou-TEU*HN+n=qZV>S3S2n1$dQit?+fd>&Iv_B0bHQDk+bf}9ge zCUl$IeAr!(t+J05FP7XeM_lLx?!PH!;-os*6RV+k}u};={T2X^y_J?4oR+ z;|@EIoXt!z$U?1=j@IXNah+IEvooXhEX=Dt6`GQEi3nx#pe}&wRHzf17FrW8%L==##RF_QMc2SsO7SU#+AsVhvSoq7DEf`^ z=^(i_Qxum|>CO{JqEa43Y54OQw_aQOb}lhK5vngyTr61CkKQ+jQ3l0T+NtC&2|Fp% z$u{Fuq4*(C92-FolXOVDaqH`HQ)KZgE0kl=;*o;!5Y@-GMNW z6lN2D@3sE(9<2zQ(9F4R^TOlz!(bfh_#%-I+dXw#y=U=O8OPFBG8l|Cq6#gqA2q+m z_9%{c?e`!y7K~A=m5-0{N!amD8&eD-fhn}{^PZSb1oZJven!>c(J2)rAx=n#JD3AH zT8Dl1P2eeFI?bZ0l^&%Zbxmv@i?8D7v^+uO{d-LfnND!vhtyDto~b_J`P_iCr3SCCDrhM7uMN4$yE@0)kO@uKqX z<`eFgEwQyS{v|R{EE>O*+`&VCGFeh(i8M0nQ;w_;LDr0M0?E0NWo(02nNoFW} zOLj;b3npIscF+Kt=B6L$$$ExhsZJ3jfA{1wj^5KXgWac)N2I|!gPjdlI^pDoQ3>R7 z#V0D!eW&A_RdHP-O$kpDh?*FgIQ(_Q*pI$QW`;}Gn}RY+q9fKhO%Z7W>w1M(FOi5p zp$U3!X%5IM3x8%)TA@mRH~0>9KyEEnb_Iv3G=7J~f}TyHvA&lgg%48QSS^I zkGpsvkJc#Jlo@mA5(98Jw_@loSiUB3sW?i+J*-%E7+@|Tfe{^NL^lRmR3pca5N_f) zd`>W?()heHc(hZ;6&g^;E?oX^o;aOrS$aojFlgJzjv5yIWWO<{|yp0bacgfh5-HLwE!S55lzfeXz)o zJMxs^qn+8>f)V4VY?7e0gTdij1=EX(GDRzS94l8WyN`}8zC7l8fvStBE6!dPOg2Xn zD8HLIRNm7$(~!}F2cLZAd#1_Y?&6(_r|$he&!IxWdq)>5iS2!5M2Kx9eLp)l%U9bf zDXFz_zvto-am4P^!E}``0=-1+@wp5VE(HbVtjsZE^I`>Ty$aTB&yPuqRCamNys=hn z{S7(4qFAf40 zawL!L-J||5+`W659#NRRIy0^&8POW3wGk*u-R0jx>{z{q}!tqC|o{^wHsJxl&DEW8jP zPX#c>Zp&m3IMV%d79o%w$j}C4{`YJptL+M&fL0j;aR;B?k_nCQ->^M1Hq-xmrq2y! zrA$D}U5BH&E!Pq7#QC4g1&k9x(0_Z1nBn(*^=aZ8%eABGAu!B&Zpr0M_5Wx4Jr@i@ zOH44pl@58AOA7Fl{pXS?IN6$68vcDN;#S=2n*(@GfpO1$OD0E~mkEy@eoe^2@IgyJtr?>66UB?wmdC#04( z<{-OYS}$WEn)f3hLIPk-*=~t4{x49U{RjNN!4Gcs{I$6rf+BVu0~S2xz*KPC1VAhO zi>ia{00xWy!Bq>j)$#!_0i?jtyge*AU;J|}h3hLs{lB5Q+ao_`>T8A8*8AsF!0AxoiX5BwJ@NcEXhiyt62b%Xf z1BK+RX2!Dn_oTO^6te*I6);6Dv(g8JqoL4e?YHj#q&eMg3)# zf8_oDq)@QC4wCls@ypHec~`2R`_pTwr2YN@0G}05IQXRe1C7Z zDJ&o3?g%st_;;*-tkC`$EfemL_gkQOkAWo`|E(r2jQV%9KlrX6#M}N}SFU?B4eb&d z7HHRd0Hwe!y5iXX58eN2)kBZgy2ro}Z-lwqmzeSYj_=P$I6oPGuR(W51VH}B=Xyj0 zC;#xjx~X$_O8^VN8zsvADDlH%>t?y@qrw(|LLFi-@G|DyXvmGTxVeYs@NP)!pMcSAzpKyZ$pyY zt?Jzn7C%9iW`7HMJD|l~xVx|Kf5L4o`~}>vZ}IPf-hI9O6ZGoi??C_dE%z_rcV7Gc z1gBd09r*93w7X;R`mN`W&-GYzSi6bzA7|T})wp)jxcl<+&*GUL*}oS4a|-%lG;duB z-Nn88Cg&%v-ltzG^;a)+?t^Bkb zb_j6F{m19JLwb&WLA24gva~h+<-qK^Z|?d{|B008@!2gU=-q4VpP)V9 zcPsQm&;OVo{z`|gYkK#(_$O!$u!#RTpZ@1M6=VYh0r>}@cW-5Xf@VSd0{Y900|D&6 T?%k6C{xo3UyVvvZdd>TP6CP>` literal 0 HcmV?d00001 diff --git a/todaystepcounterlib/proguard-rules.pro b/todaystepcounterlib/proguard-rules.pro new file mode 100644 index 0000000..1eec44e --- /dev/null +++ b/todaystepcounterlib/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/jiahongfei/Documents/DeveloperSoftware/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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/todaystepcounterlib/src/main/AndroidManifest.xml b/todaystepcounterlib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fea9fbf --- /dev/null +++ b/todaystepcounterlib/src/main/AndroidManifest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todaystepcounterlib/src/main/aidl/com/today/step/lib/ISportStepInterface.aidl b/todaystepcounterlib/src/main/aidl/com/today/step/lib/ISportStepInterface.aidl new file mode 100644 index 0000000..9913ffa --- /dev/null +++ b/todaystepcounterlib/src/main/aidl/com/today/step/lib/ISportStepInterface.aidl @@ -0,0 +1,35 @@ +// ISportStepInterface.aidl +package com.today.step.lib; + +interface ISportStepInterface { + /** + * 获取当前时间运动步数 + */ + int getCurrentTimeSportStep(); + + /** + * 获取所有步数列表,json格式,如果数据过多建议在线程中获取,否则会阻塞UI线程 + */ + String getTodaySportStepArray(); + + /** + * 根据时间获取步数列表 + * + * @param dateString 格式yyyy-MM-dd + * @return + */ + String getTodaySportStepArrayByDate(String date); + + /** + * 根据时间和天数获取步数列表 + * 例如: + * startDate = 2018-01-15 + * days = 3 + * 获取 2018-01-15、2018-01-16、2018-01-17三天的步数 + * + * @param startDate 格式yyyy-MM-dd + * @param days + * @return + */ + String getTodaySportStepArrayByStartDateAndDays(String date, int days); +} diff --git a/todaystepcounterlib/src/main/assets/microlog.properties b/todaystepcounterlib/src/main/assets/microlog.properties new file mode 100644 index 0000000..d6f16e3 --- /dev/null +++ b/todaystepcounterlib/src/main/assets/microlog.properties @@ -0,0 +1,4 @@ +# This is a simple Microlog configuration file +microlog.level=DEBUG +microlog.appender=LogCatAppender;FileAppender +microlog.formatter=SimpleFormatter diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/BaseClickBroadcast.java b/todaystepcounterlib/src/main/java/com/today/step/lib/BaseClickBroadcast.java new file mode 100644 index 0000000..01dc55b --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/BaseClickBroadcast.java @@ -0,0 +1,12 @@ +package com.today.step.lib; + +import android.content.BroadcastReceiver; + +/** + * 通知栏点击通知 + * Created by jiahongfei on 2017/10/19. + */ + +public abstract class BaseClickBroadcast extends BroadcastReceiver { + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/DateUtils.java b/todaystepcounterlib/src/main/java/com/today/step/lib/DateUtils.java new file mode 100644 index 0000000..2644d5a --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/DateUtils.java @@ -0,0 +1,72 @@ +package com.today.step.lib; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @Description: 时间工具类(时间格式转换方便类) + */ +class DateUtils { + + private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(); + + /** + * 返回一定格式的当前时间 + * + * @param pattern "yyyy-MM-dd HH:mm:ss E" + * @return + */ + public static String getCurrentDate(String pattern) { + SIMPLE_DATE_FORMAT.applyPattern(pattern); + Date date = new Date(System.currentTimeMillis()); + String dateString = SIMPLE_DATE_FORMAT.format(date); + return dateString; + + } + + public static long getDateMillis(String dateString, String pattern) { + long millionSeconds = 0; + SIMPLE_DATE_FORMAT.applyPattern(pattern); + try { + millionSeconds = SIMPLE_DATE_FORMAT.parse(dateString).getTime(); + } catch (ParseException e) { + e.printStackTrace(); + }// 毫秒 + + return millionSeconds; + } + + /** + * 格式化输入的millis + * + * @param millis + * @param pattern yyyy-MM-dd HH:mm:ss E + * @return + */ + public static String dateFormat(long millis, String pattern) { + SIMPLE_DATE_FORMAT.applyPattern(pattern); + Date date = new Date(millis); + String dateString = SIMPLE_DATE_FORMAT.format(date); + return dateString; + } + + /** + * 将dateString原来old格式转换成new格式 + * + * @param dateString + * @param oldPattern yyyy-MM-dd HH:mm:ss E + * @param newPattern + * @return oldPattern和dateString形式不一样直接返回dateString + */ + public static String dateFormat(String dateString, String oldPattern, + String newPattern) { + long millis = getDateMillis(dateString, oldPattern); + if (0 == millis) { + return dateString; + } + String date = dateFormat(millis, newPattern); + return date; + } + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/ITodayStepDBHelper.java b/todaystepcounterlib/src/main/java/com/today/step/lib/ITodayStepDBHelper.java new file mode 100644 index 0000000..61b4c71 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/ITodayStepDBHelper.java @@ -0,0 +1,29 @@ +package com.today.step.lib; + +import java.util.List; + +/** + * @author : jiahongfei + * @email : jiahongfeinew@163.com + * @date : 2018/1/22 + * @desc : + */ + +interface ITodayStepDBHelper { + + void createTable(); + + void deleteTable(); + + void clearCapacity(String curDate, int limit); + + boolean isExist(TodayStepData todayStepData); + + void insert(TodayStepData todayStepData); + + List getQueryAll(); + + List getStepListByDate(String dateString); + + List getStepListByStartDateAndDays(String startDate, int days); +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/JobSchedulerService.java b/todaystepcounterlib/src/main/java/com/today/step/lib/JobSchedulerService.java new file mode 100644 index 0000000..d76f95f --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/JobSchedulerService.java @@ -0,0 +1,35 @@ +package com.today.step.lib; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Intent; +import android.os.Build; +import android.support.annotation.RequiresApi; + +/** + * + * Created by jiahongfei on 2017/10/13. + */ + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class JobSchedulerService extends JobService { + + private static final String TAG = "JobSchedulerService"; + + @Override + public boolean onStartJob(JobParameters params) { + Intent intent = new Intent(getApplication(), TodayStepService.class); + getApplication().startService(intent); + +// Toast.makeText(getApplicationContext(), "onStartJob", Toast.LENGTH_SHORT).show(); + + Logger.e(TAG,"onStartJob"); + + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/Logger.java b/todaystepcounterlib/src/main/java/com/today/step/lib/Logger.java new file mode 100644 index 0000000..249caa6 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/Logger.java @@ -0,0 +1,66 @@ +package com.today.step.lib; + +import android.text.TextUtils; +import android.util.Log; + +/** + * 日志打印工具类,封装到一起,是为了调用时方便 + * + * @author Administrator + */ +class Logger { + private static final String TAG = "Logger"; + + public static boolean sIsDebug = BuildConfig.TODAY_STEP_DEBUG; + + public static void v(String message) { + if (sIsDebug) + Log.v(TAG, message); + } + + public static void v(String tag, String message) { + if (sIsDebug) + Log.v(tag, message); + } + + public static void d(String message) { + if (sIsDebug) + Log.d(TAG, message); + } + + public static void i(String message) { + if (sIsDebug) + Log.i(TAG, message); + } + + public static void i(String tag, String message) { + if (sIsDebug) + Log.i(tag, message); + } + + public static void w(String message) { + if (sIsDebug) + Log.w(TAG, message); + } + + public static void w(String tag, String message) { + if (sIsDebug) + Log.w(tag, message); + } + + public static void e(String message) { + if (sIsDebug) + Log.e(TAG, message); + } + + public static void e(String tag, String message) { + if (sIsDebug) + Log.e(tag, message); + } + + public static void d(String tag, String message) { + if (!TextUtils.isEmpty(message) && sIsDebug) { + Log.d(TextUtils.isEmpty(tag) ? TAG : tag, message); + } + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/Microlog4Android.java b/todaystepcounterlib/src/main/java/com/today/step/lib/Microlog4Android.java new file mode 100644 index 0000000..8f15721 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/Microlog4Android.java @@ -0,0 +1,35 @@ +package com.today.step.lib; + +import android.content.Context; + +import com.google.code.microlog4android.LoggerFactory; +import com.google.code.microlog4android.appender.FileAppender; +import com.google.code.microlog4android.config.PropertyConfigurator; + +/** + * @author : jiahongfei + * @email : jiahongfeinew@163.com + * @date : 2018/2/6 + * @desc : + */ + +public class Microlog4Android { + + private static final com.google.code.microlog4android.Logger logger = LoggerFactory.getLogger(); + + public void configure(Context context){ + if(null != logger) { + PropertyConfigurator.getConfigurator(context).configure(); + FileAppender appender = (FileAppender) logger.getAppender(1); + appender.setAppend(true); + logger.addAppender(appender); + } + } + + public void error(Object message){ + if(null != logger) { + logger.error(message); + } + } + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/OnStepCounterListener.java b/todaystepcounterlib/src/main/java/com/today/step/lib/OnStepCounterListener.java new file mode 100644 index 0000000..7cefca4 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/OnStepCounterListener.java @@ -0,0 +1,20 @@ +package com.today.step.lib; + +/** + * Created by jiahongfei on 2017/6/30. + */ + +interface OnStepCounterListener { + + /** + * 用于显示步数 + * @param step + */ + void onChangeStepCounter(int step); + + /** + * 步数清零监听,由于跨越0点需要重新计步 + */ + void onStepCounterClean(); + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/PreferencesHelper.java b/todaystepcounterlib/src/main/java/com/today/step/lib/PreferencesHelper.java new file mode 100644 index 0000000..62f4fa4 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/PreferencesHelper.java @@ -0,0 +1,119 @@ +package com.today.step.lib; + +import android.content.Context; +import android.content.SharedPreferences; + + +/** + * @ClassName: PreferencesHelper + * @Description: (公用类,用于缓存一些key——value类型的数据) + */ + +class PreferencesHelper { + + private static final String TAG = "PreferencesHelper"; + + public static final String APP_SHARD = "today_step_share_prefs"; + + // 上一次计步器的步数 + public static final String LAST_SENSOR_TIME = "last_sensor_time"; + // 步数补偿数值,每次传感器返回的步数-offset=当前步数 + public static final String STEP_OFFSET = "step_offset"; + // 当天,用来判断是否跨天 + public static final String STEP_TODAY = "step_today"; + // 清除步数 + public static final String CLEAN_STEP = "clean_step"; + // 当前步数 + public static final String CURR_STEP = "curr_step"; + //手机关机监听 + public static final String SHUTDOWN = "shutdown"; + //系统运行时间 + public static final String ELAPSED_REALTIMEl = "elapsed_realtime"; + + /** + * Get SharedPreferences + */ + private static SharedPreferences getSharedPreferences(Context context) { + return context.getSharedPreferences(APP_SHARD, Context.MODE_PRIVATE); + } + + public static void setLastSensorStep(Context context, float lastSensorStep){ + Logger.e(TAG, "setLastSensorStep"); + getSharedPreferences(context).edit().putFloat(LAST_SENSOR_TIME,lastSensorStep).commit(); + } + + public static float getLastSensorStep(Context context){ + Logger.e(TAG, "getLastSensorStep"); + return getSharedPreferences(context).getFloat(LAST_SENSOR_TIME,0.0f); + } + + public static void setStepOffset(Context context, float stepOffset){ + Logger.e(TAG, "setStepOffset"); + getSharedPreferences(context).edit().putFloat(STEP_OFFSET,stepOffset).commit(); + } + + public static float getStepOffset(Context context){ + Logger.e(TAG, "getStepOffset"); + return getSharedPreferences(context).getFloat(STEP_OFFSET,0.0f); + } + + public static void setStepToday(Context context, String stepToday){ + Logger.e(TAG, "setStepToday"); + getSharedPreferences(context).edit().putString(STEP_TODAY,stepToday).commit(); + } + + public static String getStepToday(Context context){ + Logger.e(TAG, "getStepToday"); + return getSharedPreferences(context).getString(STEP_TODAY,""); + } + + /** + * true清除步数从0开始,false否 + * @param context + * @param cleanStep + */ + public static void setCleanStep(Context context, boolean cleanStep){ + Logger.e(TAG, "setCleanStep"); + getSharedPreferences(context).edit().putBoolean(CLEAN_STEP,cleanStep).commit(); + } + + /** + * true 清除步数,false否 + * @param context + * @return + */ + public static boolean getCleanStep(Context context){ + Logger.e(TAG, "getCleanStep"); + return getSharedPreferences(context).getBoolean(CLEAN_STEP,true); + } + + public static void setCurrentStep(Context context, float currStep){ + Logger.e(TAG, "setCurrentStep"); + getSharedPreferences(context).edit().putFloat(CURR_STEP,currStep).commit(); + } + + public static float getCurrentStep(Context context){ + Logger.e(TAG, "getCurrentStep"); + return getSharedPreferences(context).getFloat(CURR_STEP,0.0f); + } + + public static void setShutdown(Context context, boolean shutdown){ + Logger.e(TAG, "setShutdown"); + getSharedPreferences(context).edit().putBoolean(SHUTDOWN,shutdown).commit(); + } + + public static boolean getShutdown(Context context){ + Logger.e(TAG, "getShutdown"); + return getSharedPreferences(context).getBoolean(SHUTDOWN, false); + } + + public static void setElapsedRealtime(Context context, long elapsedRealtime){ + Logger.e(TAG, "setElapsedRealtime"); + getSharedPreferences(context).edit().putLong(ELAPSED_REALTIMEl,elapsedRealtime).commit(); + } + + public static long getElapsedRealtime(Context context){ + Logger.e(TAG, "getElapsedRealtime"); + return getSharedPreferences(context).getLong(ELAPSED_REALTIMEl, 0L); + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/SportStepJsonUtils.java b/todaystepcounterlib/src/main/java/com/today/step/lib/SportStepJsonUtils.java new file mode 100644 index 0000000..14ebb14 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/SportStepJsonUtils.java @@ -0,0 +1,57 @@ +package com.today.step.lib; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; + +/** + * @author : jiahongfei + * @email : jiahongfeinew@163.com + * @date : 2018/1/31 + * @desc : 用于解析和生成运动步数Json字符串 + */ + +public class SportStepJsonUtils { + + public static final String SPORT_DATE = "sportDate"; + public static final String STEP_NUM = "stepNum"; + public static final String DISTANCE = "km"; + public static final String CALORIE = "kaluli"; + public static final String TODAY = TodayStepDBHelper.TODAY; + + static JSONArray getSportStepJsonArray(List todayStepDataArrayList) { + JSONArray jsonArray = new JSONArray(); + if (null == todayStepDataArrayList || 0 == todayStepDataArrayList.size()) { + return jsonArray; + } + for (int i = 0; i < todayStepDataArrayList.size(); i++) { + TodayStepData todayStepData = todayStepDataArrayList.get(i); + try { + JSONObject subObject = new JSONObject(); + subObject.put(TODAY, todayStepData.getToday()); + subObject.put(SPORT_DATE, todayStepData.getDate()); + subObject.put(STEP_NUM, todayStepData.getStep()); + subObject.put(DISTANCE, getDistanceByStep(todayStepData.getStep())); + subObject.put(CALORIE, getCalorieByStep(todayStepData.getStep())); + jsonArray.put(subObject); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return jsonArray; + } + + // 公里计算公式 + static String getDistanceByStep(long steps) { + return String.format("%.2f", steps * 0.6f / 1000); + } + + // 千卡路里计算公式 + static String getCalorieByStep(long steps) { + return String.format("%.1f", steps * 0.6f * 60 * 1.036f / 1000); + } + + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/StepAlertManagerUtils.java b/todaystepcounterlib/src/main/java/com/today/step/lib/StepAlertManagerUtils.java new file mode 100644 index 0000000..388f3b1 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/StepAlertManagerUtils.java @@ -0,0 +1,50 @@ +package com.today.step.lib; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import java.util.Calendar; + +import static android.content.Context.ALARM_SERVICE; + +/** + * Created by jiahongfei on 2017/6/18. + */ +class StepAlertManagerUtils { + + private static final String TAG = "StepAlertManagerUtils"; + + /** + * 设置0点分隔Alert,当前天+1天的0点启动 + * + * @param application + */ + public static void set0SeparateAlertManager(Context application) { + + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.add(Calendar.DAY_OF_YEAR,1); + String tomorrow = DateUtils.dateFormat(calendar.getTimeInMillis(),"yyyy-MM-dd"); + long timeInMillis = DateUtils.getDateMillis(tomorrow+ " 00:00:00","yyyy-MM-dd HH:mm:ss"); + + Logger.e(TAG, DateUtils.dateFormat(timeInMillis,"yyyy-MM-dd HH:mm:ss")); + + AlarmManager alarmManager = (AlarmManager) application.getSystemService(ALARM_SERVICE); + Intent i1 = new Intent(application, TodayStepAlertReceive.class); + i1.putExtra(TodayStepService.INTENT_NAME_0_SEPARATE, true); + i1.setAction(TodayStepAlertReceive.ACTION_STEP_ALERT); + PendingIntent operation = PendingIntent.getBroadcast(application, 0, i1, PendingIntent.FLAG_UPDATE_CURRENT); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, operation); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, operation); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, timeInMillis, operation); + } + + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepAlertReceive.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepAlertReceive.java new file mode 100644 index 0000000..0e2e2fd --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepAlertReceive.java @@ -0,0 +1,30 @@ +package com.today.step.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * 0点启动app处理步数 + * Created by jiahongfei on 2017/6/18. + */ + +public class TodayStepAlertReceive extends BroadcastReceiver { + + public static final String ACTION_STEP_ALERT = "action_step_alert"; + + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_STEP_ALERT.equals(intent.getAction())) { + boolean separate = intent.getBooleanExtra(TodayStepService.INTENT_NAME_0_SEPARATE, false); + Intent stepInent = new Intent(context, TodayStepService.class); + stepInent.putExtra(TodayStepService.INTENT_NAME_0_SEPARATE, separate); + context.startService(stepInent); + + StepAlertManagerUtils.set0SeparateAlertManager(context.getApplicationContext()); + + Logger.e("TodayStepAlertReceive","TodayStepAlertReceive"); + } + + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepBootCompleteReceiver.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepBootCompleteReceiver.java new file mode 100644 index 0000000..14b9133 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepBootCompleteReceiver.java @@ -0,0 +1,25 @@ +package com.today.step.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * 开机完成广播 + * + */ +public class TodayStepBootCompleteReceiver extends BroadcastReceiver { + + private static final String TAG = "TodayStepBootCompleteReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + + Intent todayStepIntent = new Intent(context, TodayStepService.class); + todayStepIntent.putExtra(TodayStepService.INTENT_NAME_BOOT,true); + context.startService(todayStepIntent); + + Logger.e(TAG,"TodayStepBootCompleteReceiver"); + + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepCounter.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepCounter.java new file mode 100644 index 0000000..14856c5 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepCounter.java @@ -0,0 +1,226 @@ +package com.today.step.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.os.PowerManager; +import android.os.SystemClock; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * android4.4 Sensor.TYPE_STEP_COUNTER + * 计步传感器计算当天步数,不需要后台Service + * Created by jiahongfei on 2017/6/30. + */ + +class TodayStepCounter implements SensorEventListener { + + private static final String TAG = "TodayStepCounter"; + + private int sOffsetStep = 0; + private int sCurrStep = 0; + private String mTodayDate; + private boolean mCleanStep = true; + private boolean mShutdown = false; + /** + * 用来标识对象第一次创建, + */ + private boolean mCounterStepReset = true; + + private Context mContext; + private OnStepCounterListener mOnStepCounterListener; + + private boolean mSeparate = false; + private boolean mBoot = false; + + public TodayStepCounter(Context context, OnStepCounterListener onStepCounterListener, boolean separate, boolean boot) { + this.mContext = context; + this.mSeparate = separate; + this.mBoot = boot; + this.mOnStepCounterListener = onStepCounterListener; + + WakeLockUtils.getLock(mContext); + + sCurrStep = (int) PreferencesHelper.getCurrentStep(mContext); + mCleanStep = PreferencesHelper.getCleanStep(mContext); + mTodayDate = PreferencesHelper.getStepToday(mContext); + sOffsetStep = (int) PreferencesHelper.getStepOffset(mContext); + mShutdown = PreferencesHelper.getShutdown(mContext); + Logger.e(TAG, "mShutdown : " + mShutdown); + //开机启动监听到,一定是关机开机了 + if (mBoot || shutdownBySystemRunningTime()) { + mShutdown = true; + PreferencesHelper.setShutdown(mContext, mShutdown); + Logger.e(TAG, "开机启动监听到"); + } + + dateChangeCleanStep(); + + initBroadcastReceiver(); + + updateStepCounter(); + + } + + private void initBroadcastReceiver() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_DATE_CHANGED); + BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + if (Intent.ACTION_TIME_TICK.equals(intent.getAction()) + || Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) { + + Logger.e(TAG, "ACTION_TIME_TICK"); + //service存活做0点分隔 + dateChangeCleanStep(); + + } + } + }; + mContext.registerReceiver(mBatInfoReceiver, filter); + + } + + @Override + public void onSensorChanged(SensorEvent event) { + + if (event.sensor.getType() == Sensor.TYPE_STEP_COUNTER) { + + int counterStep = (int) event.values[0]; + + if (mCleanStep) { + //TODO:只有传感器回调才会记录当前传感器步数,然后对当天步数进行清零,所以步数会少,少的步数等于传感器启动需要的步数,假如传感器需要10步进行启动,那么就少10步 + cleanStep(counterStep); + } else { + //处理关机启动 + if (mShutdown || shutdownByCounterStep(counterStep)) { + Logger.e(TAG, "onSensorChanged shutdown"); + shutdown(counterStep); + } + } + sCurrStep = counterStep - sOffsetStep; + + if (sCurrStep < 0) { + //容错处理,无论任何原因步数不能小于0,如果小于0,直接清零 + Logger.e(TAG, "容错处理,无论任何原因步数不能小于0,如果小于0,直接清零"); + cleanStep(counterStep); + } + + PreferencesHelper.setCurrentStep(mContext, sCurrStep); + PreferencesHelper.setElapsedRealtime(mContext, SystemClock.elapsedRealtime()); + PreferencesHelper.setLastSensorStep(mContext, counterStep); + + Logger.e(TAG, "counterStep : " + counterStep + " --- " + "sOffsetStep : " + sOffsetStep + " --- " + "sCurrStep : " + sCurrStep); + + updateStepCounter(); + } + } + + private void cleanStep(int counterStep) { + //清除步数,步数归零,优先级最高 + sCurrStep = 0; + sOffsetStep = counterStep; + PreferencesHelper.setStepOffset(mContext, sOffsetStep); + + mCleanStep = false; + PreferencesHelper.setCleanStep(mContext, mCleanStep); + + Logger.e(TAG, "mCleanStep : " + "清除步数,步数归零"); + } + + private void shutdown(int counterStep) { + int tmpCurrStep = (int) PreferencesHelper.getCurrentStep(mContext); + //重新设置offset + sOffsetStep = counterStep - tmpCurrStep; + PreferencesHelper.setStepOffset(mContext, sOffsetStep); + + mShutdown = false; + PreferencesHelper.setShutdown(mContext, mShutdown); + } + + private boolean shutdownByCounterStep(int counterStep) { + if (mCounterStepReset) { + //只判断一次 + if (counterStep < PreferencesHelper.getLastSensorStep(mContext)) { + //当前传感器步数小于上次传感器步数肯定是重新启动了,只是用来增加精度不是绝对的 + Logger.e(TAG, "当前传感器步数小于上次传感器步数肯定是重新启动了,只是用来增加精度不是绝对的"); + return true; + } + mCounterStepReset = false; + } + return false; + } + + private boolean shutdownBySystemRunningTime() { + if (PreferencesHelper.getElapsedRealtime(mContext) > SystemClock.elapsedRealtime()) { + //上次运行的时间大于当前运行时间判断为重启,只是增加精度,极端情况下连续重启,会判断不出来 + Logger.e(TAG, "上次运行的时间大于当前运行时间判断为重启,只是增加精度,极端情况下连续重启,会判断不出来"); + return true; + } + return false; + } + + private synchronized void dateChangeCleanStep() { + //时间改变了清零,或者0点分隔回调 + if (!getTodayDate().equals(mTodayDate) || mSeparate) { + + WakeLockUtils.getLock(mContext); + + mCleanStep = true; + PreferencesHelper.setCleanStep(mContext, mCleanStep); + + mTodayDate = getTodayDate(); + PreferencesHelper.setStepToday(mContext, mTodayDate); + + mShutdown = false; + PreferencesHelper.setShutdown(mContext, mShutdown); + + mBoot = false; + + mSeparate = false; + + sCurrStep = 0; + PreferencesHelper.setCurrentStep(mContext, sCurrStep); + + if (null != mOnStepCounterListener) { + mOnStepCounterListener.onStepCounterClean(); + } + } + } + + private String getTodayDate() { + Date date = new Date(System.currentTimeMillis()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(date); + } + + private void updateStepCounter() { + + //每次回调都判断一下是否跨天 + dateChangeCleanStep(); + + if (null != mOnStepCounterListener) { + mOnStepCounterListener.onChangeStepCounter(sCurrStep); + } + } + + public int getCurrentStep() { + sCurrStep = (int) PreferencesHelper.getCurrentStep(mContext); + return sCurrStep; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDBHelper.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDBHelper.java new file mode 100644 index 0000000..a7f582c --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDBHelper.java @@ -0,0 +1,201 @@ +package com.today.step.lib; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 用来记录当天步数列表,传感器回调30次记录一条数据 + * Created by jiahongfei on 2017/10/9. + */ + +class TodayStepDBHelper extends SQLiteOpenHelper implements ITodayStepDBHelper{ + + private static final String TAG = "TodayStepDBHelper"; + + private static final String DATE_PATTERN_YYYY_MM_DD = "yyyy-MM-dd"; + + private static final int VERSION = 1; + private static final String DATABASE_NAME = "TodayStepDB.db"; + private static final String TABLE_NAME = "TodayStepData"; + private static final String PRIMARY_KEY = "_id"; + public static final String TODAY = "today"; + public static final String DATE = "date"; + public static final String STEP = "step"; + + private static final String SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + + PRIMARY_KEY + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + TODAY + " TEXT, " + + DATE + " long, " + + STEP + " long);"; + private static final String SQL_DELETE_TABLE = "DROP TABLE IF EXISTS " + TABLE_NAME; + private static final String SQL_QUERY_ALL = "SELECT * FROM " + TABLE_NAME; + private static final String SQL_QUERY_STEP = "SELECT * FROM " + TABLE_NAME + " WHERE " + TODAY + " = ? AND " + STEP + " = ?"; + private static final String SQL_QUERY_STEP_BY_DATE = "SELECT * FROM " + TABLE_NAME + " WHERE " + TODAY + " = ?"; + private static final String SQL_DELETE_TODAY = "DELETE FROM " + TABLE_NAME + " WHERE " + TODAY + " = ?"; + + //只保留mLimit天的数据 + private int mLimit = -1; + + public static ITodayStepDBHelper factory(Context context){ + return new TodayStepDBHelper(context); + } + + private TodayStepDBHelper(Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + + Logger.e(TAG, SQL_CREATE_TABLE); + db.execSQL(SQL_CREATE_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + deleteTable(); + onCreate(db); + } + + @Override + public synchronized boolean isExist(TodayStepData todayStepData) { + Cursor cursor = getReadableDatabase().rawQuery(SQL_QUERY_STEP, new String[]{todayStepData.getToday(), todayStepData.getStep() + ""}); + boolean exist = cursor.getCount() > 0 ? true : false; + cursor.close(); + return exist; + } + + @Override + public synchronized void createTable() { + getWritableDatabase().execSQL(SQL_CREATE_TABLE); + } + + @Override + public synchronized void insert(TodayStepData todayStepData) { + + ContentValues contentValues = new ContentValues(); + contentValues.put(TODAY, todayStepData.getToday()); + contentValues.put(DATE, todayStepData.getDate()); + contentValues.put(STEP, todayStepData.getStep()); + getWritableDatabase().insert(TABLE_NAME, null, contentValues); + } + + @Override + public synchronized List getQueryAll() { + Cursor cursor = getReadableDatabase().rawQuery(SQL_QUERY_ALL, new String[]{}); + List todayStepDatas = getTodayStepDataList(cursor); + cursor.close(); + return todayStepDatas; + } + + /** + * 根据时间获取步数列表 + * + * @param dateString 格式yyyy-MM-dd + * @return + */ + @Override + public synchronized List getStepListByDate(String dateString) { + Cursor cursor = getReadableDatabase().rawQuery(SQL_QUERY_STEP_BY_DATE, new String[]{dateString}); + List todayStepDatas = getTodayStepDataList(cursor); + cursor.close(); + return todayStepDatas; + } + + /** + * 根据时间和天数获取步数列表 + * 例如: + * startDate = 2018-01-15 + * days = 3 + * 获取 2018-01-15、2018-01-16、2018-01-17三天的步数 + * + * @param startDate 格式yyyy-MM-dd + * @param days + * @return + */ + @Override + public synchronized List getStepListByStartDateAndDays(String startDate, int days) { + List todayStepDatas = new ArrayList<>(); + for (int i = 0; i < days; i++) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(DateUtils.getDateMillis(startDate, DATE_PATTERN_YYYY_MM_DD)); + calendar.add(Calendar.DAY_OF_YEAR, i); + Cursor cursor = getReadableDatabase().rawQuery(SQL_QUERY_STEP_BY_DATE, + new String[]{DateUtils.dateFormat(calendar.getTimeInMillis(), DATE_PATTERN_YYYY_MM_DD)}); + todayStepDatas.addAll(getTodayStepDataList(cursor)); + cursor.close(); + } + return todayStepDatas; + } + + private List getTodayStepDataList(Cursor cursor) { + + List todayStepDatas = new ArrayList<>(); + while (cursor.moveToNext()) { + String today = cursor.getString(cursor.getColumnIndex(TODAY)); + long date = cursor.getLong(cursor.getColumnIndex(DATE)); + long step = cursor.getLong(cursor.getColumnIndex(STEP)); + TodayStepData todayStepData = new TodayStepData(); + todayStepData.setToday(today); + todayStepData.setDate(date); + todayStepData.setStep(step); + todayStepDatas.add(todayStepData); + } + return todayStepDatas; + } + + /** + * 根据limit来清除数据库 + * 例如: + * curDate = 2018-01-10 limit=0;表示只保留2018-01-10 + * curDate = 2018-01-10 limit=1;表示保留2018-01-10、2018-01-09等 + * @param curDate + * @param limit -1失效 + */ + @Override + public synchronized void clearCapacity(String curDate, int limit) { + mLimit = limit; + if (mLimit <= 0) { + return; + } + try { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(DateUtils.getDateMillis(curDate, DATE_PATTERN_YYYY_MM_DD)); + calendar.add(Calendar.DAY_OF_YEAR, -(mLimit)); + String date = DateUtils.dateFormat(calendar.getTimeInMillis(), DATE_PATTERN_YYYY_MM_DD); + Log.e(TAG, date); + + List todayStepDataList = getQueryAll(); + Set delDateSet = new HashSet<>(); + for (TodayStepData tmpTodayStepData : todayStepDataList) { + long dbTodayDate = DateUtils.getDateMillis(tmpTodayStepData.getToday(), DATE_PATTERN_YYYY_MM_DD); + if (calendar.getTimeInMillis() >= dbTodayDate) { + delDateSet.add(tmpTodayStepData.getToday()); + } + } + + for (String delDate : delDateSet) { + getWritableDatabase().execSQL(SQL_DELETE_TODAY, new String[]{delDate}); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public synchronized void deleteTable() { + getWritableDatabase().execSQL(SQL_DELETE_TABLE); + } + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepData.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepData.java new file mode 100644 index 0000000..6f7b986 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepData.java @@ -0,0 +1,84 @@ +package com.today.step.lib; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.Serializable; + + +public class TodayStepData implements Serializable, Parcelable { + + //当天时间,只显示到天 yyyy-MM-dd + private String today; + //步数时间,显示到毫秒 + private long date; + //对应date时间的步数 + private long step; + + public TodayStepData() { + + } + + protected TodayStepData(Parcel in) { + today = in.readString(); + date = in.readLong(); + step = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(today); + dest.writeLong(date); + dest.writeLong(step); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public TodayStepData createFromParcel(Parcel in) { + return new TodayStepData(in); + } + + @Override + public TodayStepData[] newArray(int size) { + return new TodayStepData[size]; + } + }; + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + public long getStep() { + return step; + } + + public void setStep(long step) { + this.step = step; + } + + public String getToday() { + return today; + } + + public void setToday(String today) { + this.today = today; + } + + @Override + public String toString() { + return "TodayStepData{" + + ", today=" + today + + ", date=" + date + + ", step=" + step + + '}'; + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDetector.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDetector.java new file mode 100644 index 0000000..0f123ed --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepDetector.java @@ -0,0 +1,309 @@ +package com.today.step.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.os.PowerManager; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Sensor.TYPE_ACCELEROMETER + * 加速度传感器计算当天步数,需要保持后台Service + */ +class TodayStepDetector implements SensorEventListener{ + + private final String TAG = "TodayStepDetector"; + + //存放三轴数据 + float[] oriValues = new float[3]; + final int ValueNum = 4; + //用于存放计算阈值的波峰波谷差值 + float[] tempValue = new float[ValueNum]; + int tempCount = 0; + //是否上升的标志位 + boolean isDirectionUp = false; + //持续上升次数 + int continueUpCount = 0; + //上一点的持续上升的次数,为了记录波峰的上升次数 + int continueUpFormerCount = 0; + //上一点的状态,上升还是下降 + boolean lastStatus = false; + //波峰值 + float peakOfWave = 0; + //波谷值 + float valleyOfWave = 0; + //此次波峰的时间 + long timeOfThisPeak = 0; + //上次波峰的时间 + long timeOfLastPeak = 0; + //当前的时间 + long timeOfNow = 0; + //当前传感器的值 + float gravityNew = 0; + //上次传感器的值 + float gravityOld = 0; + //动态阈值需要动态的数据,这个值用于这些动态数据的阈值 + final float InitialValue = (float) 1.3; + //初始阈值 + float ThreadValue = (float) 2.0; + //波峰波谷时间差 + int TimeInterval = 400; + + private int count = 0; + private int mCount = 0; + private OnStepCounterListener mOnStepCounterListener; + private Context mContext; + private long timeOfLastPeak1 = 0; + private long timeOfThisPeak1 = 0; + private String mTodayDate; + + public TodayStepDetector(Context context, OnStepCounterListener onStepCounterListener){ + super(); + mContext = context; + this.mOnStepCounterListener = onStepCounterListener; + + WakeLockUtils.getLock(mContext); + + mCount = (int) PreferencesHelper.getCurrentStep(mContext); + mTodayDate = PreferencesHelper.getStepToday(mContext); + dateChangeCleanStep(); + initBroadcastReceiver(); + + updateStepCounter(); + + } + + private void initBroadcastReceiver() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_DATE_CHANGED); + BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + if (Intent.ACTION_TIME_TICK.equals(intent.getAction()) + || Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) { + Logger.e(TAG, "ACTION_TIME_TICK"); + //service存活做0点分隔 + dateChangeCleanStep(); + + } + } + }; + mContext.registerReceiver(mBatInfoReceiver, filter); + } + + private synchronized void dateChangeCleanStep() { + //时间改变了清零,或者0点分隔回调 + if (!getTodayDate().equals(mTodayDate)) { + + WakeLockUtils.getLock(mContext); + + mCount = 0; + PreferencesHelper.setCurrentStep(mContext, mCount); + + mTodayDate = getTodayDate(); + PreferencesHelper.setStepToday(mContext, mTodayDate); + + setSteps(0); + + if(null != mOnStepCounterListener){ + mOnStepCounterListener.onStepCounterClean(); + } + } + } + + private String getTodayDate() { + Date date = new Date(System.currentTimeMillis()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(date); + } + + private void updateStepCounter(){ + + //每次回调都判断一下是否跨天 + dateChangeCleanStep(); + + if (null != mOnStepCounterListener) { + mOnStepCounterListener.onChangeStepCounter(mCount); + } + } + + @Override + public void onSensorChanged(SensorEvent event) { + for (int i = 0; i < 3; i++) { + oriValues[i] = event.values[i]; + } + gravityNew = (float) Math.sqrt(oriValues[0] * oriValues[0] + + oriValues[1] * oriValues[1] + oriValues[2] * oriValues[2]); + detectorNewStep(gravityNew); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // + } + + /* + * 检测步子,并开始计步 + * 1.传入sersor中的数据 + * 2.如果检测到了波峰,并且符合时间差以及阈值的条件,则判定为1步 + * 3.符合时间差条件,波峰波谷差值大于initialValue,则将该差值纳入阈值的计算中 + * */ + private void detectorNewStep(float values) { + if (gravityOld == 0) { + gravityOld = values; + } else { + if (detectorPeak(values, gravityOld)) { + timeOfLastPeak = timeOfThisPeak; + timeOfNow = System.currentTimeMillis(); + if (timeOfNow - timeOfLastPeak >= TimeInterval + && (peakOfWave - valleyOfWave >= ThreadValue)) { + timeOfThisPeak = timeOfNow; + /* + * 更新界面的处理,不涉及到算法 + * 一般在通知更新界面之前,增加下面处理,为了处理无效运动: + * 1.连续记录10才开始计步 + * 2.例如记录的9步用户停住超过3秒,则前面的记录失效,下次从头开始 + * 3.连续记录了9步用户还在运动,之前的数据才有效 + * */ + countStep(); + } + if (timeOfNow - timeOfLastPeak >= TimeInterval + && (peakOfWave - valleyOfWave >= InitialValue)) { + timeOfThisPeak = timeOfNow; + ThreadValue = peakValleyThread(peakOfWave - valleyOfWave); + } + } + } + gravityOld = values; + } + + /* + * 检测波峰 + * 以下四个条件判断为波峰: + * 1.目前点为下降的趋势:isDirectionUp为false + * 2.之前的点为上升的趋势:lastStatus为true + * 3.到波峰为止,持续上升大于等于2次 + * 4.波峰值大于20 + * 记录波谷值 + * 1.观察波形图,可以发现在出现步子的地方,波谷的下一个就是波峰,有比较明显的特征以及差值 + * 2.所以要记录每次的波谷值,为了和下次的波峰做对比 + * */ + private boolean detectorPeak(float newValue, float oldValue) { + lastStatus = isDirectionUp; + if (newValue >= oldValue) { + isDirectionUp = true; + continueUpCount++; + } else { + continueUpFormerCount = continueUpCount; + continueUpCount = 0; + isDirectionUp = false; + } + + if (!isDirectionUp && lastStatus + && (continueUpFormerCount >= 2 || oldValue >= 20)) { + peakOfWave = oldValue; + return true; + } else if (!lastStatus && isDirectionUp) { + valleyOfWave = oldValue; + return false; + } else { + return false; + } + } + + /* + * 阈值的计算 + * 1.通过波峰波谷的差值计算阈值 + * 2.记录4个值,存入tempValue[]数组中 + * 3.在将数组传入函数averageValue中计算阈值 + * */ + private float peakValleyThread(float value) { + float tempThread = ThreadValue; + if (tempCount < ValueNum) { + tempValue[tempCount] = value; + tempCount++; + } else { + tempThread = averageValue(tempValue, ValueNum); + for (int i = 1; i < ValueNum; i++) { + tempValue[i - 1] = tempValue[i]; + } + tempValue[ValueNum - 1] = value; + } + return tempThread; + + } + + /* + * 梯度化阈值 + * 1.计算数组的均值 + * 2.通过均值将阈值梯度化在一个范围里 + * */ + private float averageValue(float value[], int n) { + float ave = 0; + for (int i = 0; i < n; i++) { + ave += value[i]; + } + ave = ave / ValueNum; + if (ave >= 8) + ave = (float) 4.3; + else if (ave >= 7 && ave < 8) + ave = (float) 3.3; + else if (ave >= 4 && ave < 7) + ave = (float) 2.3; + else if (ave >= 3 && ave < 4) + ave = (float) 2.0; + else { + ave = (float) 1.7; + } + return ave; + } + + + + + /* + * 连续走十步才会开始计步 + * 连续走了9步以下,停留超过3秒,则计数清空 + * */ + private void countStep() { + this.timeOfLastPeak1 = this.timeOfThisPeak1; + this.timeOfThisPeak1 = System.currentTimeMillis(); + if (this.timeOfThisPeak1 - this.timeOfLastPeak1 <= 3000L){ + if(this.count<9){ + this.count++; + }else if(this.count == 9){ + this.count++; + this.mCount += this.count; + PreferencesHelper.setCurrentStep(mContext, mCount); + updateStepCounter(); + }else{ + this.mCount++; + PreferencesHelper.setCurrentStep(mContext, mCount); + updateStepCounter(); + } + }else{//超时 + this.count = 1;//为1,不是0 + } + + } + + + private void setSteps(int initValue) { + this.mCount = initValue; + this.count = 0; + timeOfLastPeak1 = 0; + timeOfThisPeak1 = 0; + } + + public int getCurrentStep() { + return mCount; + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepManager.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepManager.java new file mode 100644 index 0000000..937d146 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepManager.java @@ -0,0 +1,63 @@ +package com.today.step.lib; + +import android.app.Application; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.support.annotation.RequiresApi; + +/** + * 计步SDK初始化方法 + * Created by jiahongfei on 2017/10/9. + */ + +public class TodayStepManager { + + private static final String TAG = "TodayStepManager"; + private static final int JOB_ID = 100; + + /** + * 在程序的最开始调用,最好在自定义的application oncreate中调用 + * + * @param application + */ + public static void init(Application application) { + + StepAlertManagerUtils.set0SeparateAlertManager(application); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { +// initJobScheduler(application); + } + + + startTodayStepService(application); + } + + public static void startTodayStepService(Application application) { + Intent intent = new Intent(application, TodayStepService.class); + application.startService(intent); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private static void initJobScheduler(Application application) { + Logger.e(TAG, "initJobScheduler"); + + JobScheduler jobScheduler = (JobScheduler) + application.getSystemService(Context.JOB_SCHEDULER_SERVICE); + JobInfo.Builder builder = new JobInfo.Builder( + JOB_ID, + new ComponentName(application.getPackageName(), JobSchedulerService.class.getName())); + builder.setMinimumLatency(5000)// 设置任务运行最少延迟时间 + .setOverrideDeadline(60000)// 设置deadline,若到期还没有达到规定的条件则会开始执行 + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)// 设置网络条件 + .setRequiresCharging(true)// 设置是否充电的条件 + .setRequiresDeviceIdle(false);// 设置手机是否空闲的条件 + int resultCode = jobScheduler.schedule(builder.build()); + if (JobScheduler.RESULT_FAILURE == resultCode) { + Logger.e(TAG, "jobScheduler 失败"); + } + } +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepService.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepService.java new file mode 100644 index 0000000..8ff48f0 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepService.java @@ -0,0 +1,467 @@ +package com.today.step.lib; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.BitmapFactory; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.support.v7.app.NotificationCompat; +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONArray; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import static com.today.step.lib.SportStepJsonUtils.getCalorieByStep; +import static com.today.step.lib.SportStepJsonUtils.getDistanceByStep; + +public class TodayStepService extends Service implements Handler.Callback { + + private static final String TAG = "TodayStepService"; + + /** + * 数据库中保存多少天的运动数据 + */ + private static final int DB_LIMIT = 2; + + //保存数据库频率 + private static final int DB_SAVE_COUNTER = 50; + + //传感器的采样周期,这里使用SensorManager.SENSOR_DELAY_FASTEST,如果使用SENSOR_DELAY_UI会导致部分手机后台清理内存之后传感器不记步 + private static final int SAMPLING_PERIOD_US = SensorManager.SENSOR_DELAY_FASTEST; + + private static final int HANDLER_WHAT_SAVE_STEP = 0; + //如果走路如果停止,10秒钟后保存数据库 + private static final int LAST_SAVE_STEP_DURATION = 10*1000; + + private static final int BROADCAST_REQUEST_CODE = 100; + + public static final String INTENT_NAME_0_SEPARATE = "intent_name_0_separate"; + public static final String INTENT_NAME_BOOT = "intent_name_boot"; + public static final String INTENT_JOB_SCHEDULER = "intent_job_scheduler"; + + public static int CURRENT_SETP = 0; + + private SensorManager sensorManager; + // private TodayStepDcretor stepDetector; + private TodayStepDetector mStepDetector; + private TodayStepCounter stepCounter; + + private NotificationManager nm; + Notification notification; + private NotificationCompat.Builder builder; + + private boolean mSeparate = false; + private boolean mBoot = false; + + private int mDbSaveCount = 0; + + private ITodayStepDBHelper mTodayStepDBHelper; + + private final Handler sHandler = new Handler(this); + + private Microlog4Android mMicrolog4Android = new Microlog4Android(); + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case HANDLER_WHAT_SAVE_STEP: { + Logger.e(TAG, "HANDLER_WHAT_SAVE_STEP"); + + microlog4AndroidError("HANDLER_WHAT_SAVE_STEP"); + + mDbSaveCount = 0; + + saveDb(true, CURRENT_SETP); + break; + } + default: + break; + } + return false; + } + + @Override + public void onCreate() { + Logger.e(TAG, "onCreate:" + CURRENT_SETP); + super.onCreate(); + + mTodayStepDBHelper = TodayStepDBHelper.factory(getApplicationContext()); + + sensorManager = (SensorManager) this + .getSystemService(SENSOR_SERVICE); + + initNotification(CURRENT_SETP); + + if(null != mMicrolog4Android) { + mMicrolog4Android.configure(this); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Logger.e(TAG, "onStartCommand:" + CURRENT_SETP); + + if (null != intent) { + mSeparate = intent.getBooleanExtra(INTENT_NAME_0_SEPARATE, false); + mBoot = intent.getBooleanExtra(INTENT_NAME_BOOT, false); + } + + mDbSaveCount = 0; + + updateNotification(CURRENT_SETP); + + //注册传感器 + startStepDetector(); + + //TODO:测试数据Start +// if(Logger.sIsDebug) { +// if (!isStepCounter()) { +// Toast.makeText(getApplicationContext(), "Lib 当前手机没有计步传感器", Toast.LENGTH_LONG).show(); +// } else { +// Toast.makeText(getApplicationContext(), "Lib 当前手机使用计步传感器", Toast.LENGTH_LONG).show(); +// +// } +// } + //TODO:测试数据End + + microlog4AndroidError("onStartCommand"); + + return START_STICKY; + } + + private void initNotification(int currentStep) { + + builder = new NotificationCompat.Builder(this); + builder.setPriority(Notification.PRIORITY_MIN); + + String receiverName = getReceiver(getApplicationContext()); + PendingIntent contentIntent = PendingIntent.getBroadcast(this, BROADCAST_REQUEST_CODE, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT); + if (!TextUtils.isEmpty(receiverName)) { + try { + contentIntent = PendingIntent.getBroadcast(this, BROADCAST_REQUEST_CODE, new Intent(this, Class.forName(receiverName)), PendingIntent.FLAG_UPDATE_CURRENT); + } catch (Exception e) { + e.printStackTrace(); + contentIntent = PendingIntent.getBroadcast(this, BROADCAST_REQUEST_CODE, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT); + } + } + builder.setContentIntent(contentIntent); + int smallIcon = getResources().getIdentifier("icon_step_small", "mipmap", getPackageName()); + if (0 != smallIcon) { + Logger.e(TAG, "smallIcon"); + builder.setSmallIcon(smallIcon); + } else { + builder.setSmallIcon(R.mipmap.ic_notification_default);// 设置通知小ICON + } + int largeIcon = getResources().getIdentifier("icon_step_large", "mipmap", getPackageName()); + if (0 != largeIcon) { + Logger.e(TAG, "largeIcon"); + builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), largeIcon)); + } else { + builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_notification_default)); + + } + builder.setTicker(getString(R.string.app_name)); + builder.setContentTitle(getString(R.string.title_notification_bar, String.valueOf(currentStep))); + String km = getDistanceByStep(currentStep); + String calorie = getCalorieByStep(currentStep); + builder.setContentText(calorie + " 千卡 " + km + " 公里"); + + //设置不可清除 + builder.setOngoing(true); + notification = builder.build(); + //将Service设置前台,这里的id和notify的id一定要相同否则会出现后台清理内存Service被杀死通知还存在的bug + startForeground(R.string.app_name, notification); + nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + nm.notify(R.string.app_name, notification); + + } + + @Override + public IBinder onBind(Intent intent) { + Logger.e(TAG, "onBind:" + CURRENT_SETP); + return mIBinder.asBinder(); + } + + private void startStepDetector() { + +// getLock(this); + + //android4.4以后如果有stepcounter可以使用计步传感器 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isStepCounter()) { + addStepCounterListener(); + } else { + addBasePedoListener(); + } + } + + private void addStepCounterListener() { + Logger.e(TAG, "addStepCounterListener"); + if (null != stepCounter) { + Logger.e(TAG, "已经注册TYPE_STEP_COUNTER"); + WakeLockUtils.getLock(this); + CURRENT_SETP = stepCounter.getCurrentStep(); + updateNotification(CURRENT_SETP); + return; + } + Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); + if (null == countSensor) { + return; + } + stepCounter = new TodayStepCounter(getApplicationContext(), mOnStepCounterListener, mSeparate, mBoot); + Logger.e(TAG, "countSensor"); + sensorManager.registerListener(stepCounter, countSensor, SAMPLING_PERIOD_US); + } + + private void addBasePedoListener() { + Logger.e(TAG, "addBasePedoListener"); + if (null != mStepDetector) { + WakeLockUtils.getLock(this); + Logger.e(TAG, "已经注册TYPE_ACCELEROMETER"); + CURRENT_SETP = mStepDetector.getCurrentStep(); + updateNotification(CURRENT_SETP); + return; + } + //没有计步器的时候开启定时器保存数据 + Sensor sensor = sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (null == sensor) { + return; + } + mStepDetector = new TodayStepDetector(this, mOnStepCounterListener); + Log.e(TAG, "TodayStepDcretor"); + // 获得传感器的类型,这里获得的类型是加速度传感器 + // 此方法用来注册,只有注册过才会生效,参数:SensorEventListener的实例,Sensor的实例,更新速率 + sensorManager.registerListener(mStepDetector, sensor, SAMPLING_PERIOD_US); + } + + @Override + public void onDestroy() { + Logger.e(TAG, "onDestroy:" + CURRENT_SETP); + + Intent intent = new Intent(this, TodayStepService.class); + startService(intent); + super.onDestroy(); + } + + @Override + public boolean onUnbind(Intent intent) { + Logger.e(TAG, "onUnbind:" + CURRENT_SETP); + return super.onUnbind(intent); + } + + /** + * 步数每次回调的方法 + * + * @param currentStep + */ + private void updateTodayStep(int currentStep) { + + microlog4AndroidError(" currentStep : " + currentStep); + + CURRENT_SETP = currentStep; + updateNotification(CURRENT_SETP); + saveStep(currentStep); + } + + private void saveStep(int currentStep) { + sHandler.removeMessages(HANDLER_WHAT_SAVE_STEP); + sHandler.sendEmptyMessageDelayed(HANDLER_WHAT_SAVE_STEP, LAST_SAVE_STEP_DURATION); + + microlog4AndroidError(" mDbSaveCount : " + mDbSaveCount); + + if (DB_SAVE_COUNTER > mDbSaveCount) { + mDbSaveCount++; + return; + } + mDbSaveCount = 0; + + saveDb(false, currentStep); + } + + /** + * @param handler true handler回调保存步数,否false + * @param currentStep + */ + private void saveDb(boolean handler, int currentStep) { + + TodayStepData todayStepData = new TodayStepData(); + todayStepData.setToday(getTodayDate()); + todayStepData.setDate(System.currentTimeMillis()); + todayStepData.setStep(currentStep); + if (null != mTodayStepDBHelper) { + Logger.e(TAG, "saveDb handler : " + handler); + if (!handler || !mTodayStepDBHelper.isExist(todayStepData)) { + Logger.e(TAG, "saveDb currentStep : " + currentStep); + + microlog4AndroidError("saveDb currentStep : " + currentStep); + + mTodayStepDBHelper.insert(todayStepData); + } + } + } + + private void cleanDb() { + + Logger.e(TAG, "cleanDb"); + + mDbSaveCount = 0; + + if (null != mTodayStepDBHelper) { + mTodayStepDBHelper.clearCapacity(DateUtils.dateFormat(System.currentTimeMillis(), "yyyy-MM-dd"), DB_LIMIT); + } + +// if (null != mTodayStepDBHelper) { + //保存多天的步数 +// mTodayStepDBHelper.deleteTable(); +// mTodayStepDBHelper.createTable(); +// } + } + + private String getTodayDate() { + Date date = new Date(System.currentTimeMillis()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(date); + } + + /** + * 更新通知 + */ + private void updateNotification(int stepCount) { + if (null == builder || null == nm) { + return; + } + builder.setContentTitle(getString(R.string.title_notification_bar, String.valueOf(stepCount))); + String km = getDistanceByStep(stepCount); + String calorie = getCalorieByStep(stepCount); + builder.setContentText(calorie + " 千卡 " + km + " 公里"); + notification = builder.build(); + nm.notify(R.string.app_name, notification); + } + + private boolean isStepCounter() { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_COUNTER); + } + + private boolean isStepDetector() { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR); + } + + private OnStepCounterListener mOnStepCounterListener = new OnStepCounterListener() { + @Override + public void onChangeStepCounter(int step) { + updateTodayStep(step); + } + + @Override + public void onStepCounterClean() { + + CURRENT_SETP = 0; + updateNotification(CURRENT_SETP); + + cleanDb(); + } + + }; + + private final ISportStepInterface.Stub mIBinder = new ISportStepInterface.Stub() { + + @Override + public int getCurrentTimeSportStep() throws RemoteException { + return CURRENT_SETP; + } + + private JSONArray getSportStepJsonArray(List todayStepDataArrayList) { + return SportStepJsonUtils.getSportStepJsonArray(todayStepDataArrayList); + } + + @Override + public String getTodaySportStepArray() throws RemoteException { + if (null != mTodayStepDBHelper) { + List todayStepDataArrayList = mTodayStepDBHelper.getQueryAll(); + JSONArray jsonArray = getSportStepJsonArray(todayStepDataArrayList); + Logger.e(TAG, jsonArray.toString()); + return jsonArray.toString(); + } + return null; + } + + @Override + public String getTodaySportStepArrayByDate(String date) throws RemoteException { + if (null != mTodayStepDBHelper) { + List todayStepDataArrayList = mTodayStepDBHelper.getStepListByDate(date); + JSONArray jsonArray = getSportStepJsonArray(todayStepDataArrayList); + Logger.e(TAG, jsonArray.toString()); + return jsonArray.toString(); + } + return null; + } + + @Override + public String getTodaySportStepArrayByStartDateAndDays(String date, int days) throws RemoteException { + if (null != mTodayStepDBHelper) { + List todayStepDataArrayList = mTodayStepDBHelper.getStepListByStartDateAndDays(date, days); + JSONArray jsonArray = getSportStepJsonArray(todayStepDataArrayList); + Logger.e(TAG, jsonArray.toString()); + return jsonArray.toString(); + } + return null; + } + }; + + public static String getReceiver(Context context) { + try { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_RECEIVERS); + ActivityInfo[] activityInfos = packageInfo.receivers; + if (null != activityInfos && activityInfos.length > 0) { + for (int i = 0; i < activityInfos.length; i++) { + String receiverName = activityInfos[i].name; + Class superClazz = Class.forName(receiverName).getSuperclass(); + int count = 1; + while (null != superClazz) { + if (superClazz.getName().equals("java.lang.Object")) { + break; + } + if (superClazz.getName().equals(BaseClickBroadcast.class.getName())) { + Log.e(TAG, "receiverName : " + receiverName); + return receiverName; + } + if (count > 20) { + //用来做容错,如果20个基类还不到Object直接跳出防止while死循环 + break; + } + count++; + superClazz = superClazz.getSuperclass(); + Log.e(TAG, "superClazz : " + superClazz); + + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private void microlog4AndroidError(String msg){ + if (null != mMicrolog4Android) { + mMicrolog4Android.error(DateUtils.getCurrentDate("yyyy-MM-dd HH:mm:ss") + " " + msg); + } + } + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepShutdownReceiver.java b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepShutdownReceiver.java new file mode 100644 index 0000000..c47df83 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/TodayStepShutdownReceiver.java @@ -0,0 +1,23 @@ +package com.today.step.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * Created by jiahongfei on 2017/9/27. + */ + +public class TodayStepShutdownReceiver extends BroadcastReceiver { + + private static final String TAG = "TodayStepShutdownReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { + Logger.e(TAG,"TodayStepShutdownReceiver"); + PreferencesHelper.setShutdown(context,true); + } + } + +} diff --git a/todaystepcounterlib/src/main/java/com/today/step/lib/WakeLockUtils.java b/todaystepcounterlib/src/main/java/com/today/step/lib/WakeLockUtils.java new file mode 100644 index 0000000..2db3788 --- /dev/null +++ b/todaystepcounterlib/src/main/java/com/today/step/lib/WakeLockUtils.java @@ -0,0 +1,39 @@ +package com.today.step.lib; + +import android.content.Context; +import android.os.PowerManager; + +import java.util.Calendar; + +/** + * @author : jiahongfei + * @email : jiahongfeinew@163.com + * @date : 2018/2/12 + * @desc : + */ + +class WakeLockUtils { + + private static PowerManager.WakeLock mWakeLock; + + synchronized static PowerManager.WakeLock getLock(Context context) { + if (mWakeLock != null) { + if (mWakeLock.isHeld()) + mWakeLock.release(); + mWakeLock = null; + } + + if (mWakeLock == null) { + PowerManager mgr = (PowerManager) context + .getSystemService(Context.POWER_SERVICE); + mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + TodayStepService.class.getName()); + mWakeLock.setReferenceCounted(true); + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + mWakeLock.acquire(); + } + return (mWakeLock); + } + +} diff --git a/todaystepcounterlib/src/main/res/mipmap-xxxhdpi/ic_notification_default.png b/todaystepcounterlib/src/main/res/mipmap-xxxhdpi/ic_notification_default.png new file mode 100644 index 0000000000000000000000000000000000000000..aee44e138434630332d88b1680f33c4b24c70ab3 GIT binary patch literal 10486 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 + TodayStepCounterLib + 当前步数:%1$s + diff --git a/todaystepcounterlib/src/test/java/com.today.step.lib/MyRobolectricTestRunner.java b/todaystepcounterlib/src/test/java/com.today.step.lib/MyRobolectricTestRunner.java new file mode 100644 index 0000000..4ca5cd1 --- /dev/null +++ b/todaystepcounterlib/src/test/java/com.today.step.lib/MyRobolectricTestRunner.java @@ -0,0 +1,53 @@ +package com.today.step.lib; + +import org.junit.runners.model.InitializationError; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.manifest.AndroidManifest; +import org.robolectric.res.Fs; + +/** + * @author : jiahongfei + * @email : jiahongfeinew@163.com + * @date : 2018/1/22 + * @desc : + */ + +public class MyRobolectricTestRunner extends RobolectricTestRunner { + /** + * Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file + * and res directory by default. Use the {@link Config} annotation to configure. + * + * @param testClass the test class to be run + * @throws InitializationError if junit says so + */ + public MyRobolectricTestRunner(Class testClass) throws InitializationError { + super(testClass); + } + + @Override + protected AndroidManifest getAppManifest(Config config) { + String projectName = "TodayStepCounter"; + int nameLength = projectName.length(); + String rootPath = System.getProperty("user.dir", "./"); + int index = rootPath.indexOf(projectName); + if (index == -1) { + throw new RuntimeException("project name not found in user.dir"); + } + //获取项目的根目录 + rootPath = rootPath.substring(0, index + nameLength); + String manifestProperty = rootPath + "/todaystepcounterlib/src/main/AndroidManifest.xml"; + String resProperty = rootPath + "/todaystepcounterlib/src/main/res"; + String assetsProperty = rootPath + "/todaystepcounterlib/src/main/assets"; + return new AndroidManifest( + Fs.fileFromPath(manifestProperty), + Fs.fileFromPath(resProperty), + Fs.fileFromPath(assetsProperty)) { + @Override + public int getTargetSdkVersion() { + return 21; + } + }; + } + +} diff --git a/todaystepcounterlib/src/test/java/com.today.step.lib/TodayStepDBHelperTest.java b/todaystepcounterlib/src/test/java/com.today.step.lib/TodayStepDBHelperTest.java new file mode 100644 index 0000000..447b0da --- /dev/null +++ b/todaystepcounterlib/src/test/java/com.today.step.lib/TodayStepDBHelperTest.java @@ -0,0 +1,110 @@ +package com.today.step.lib; + +import android.app.Application; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.Calendar; +import java.util.List; + +/** + * @author : jiahongfei + * @email : jiahongfeinew@163.com + * @date : 2018/1/22 + * @desc : + */ +@RunWith(MyRobolectricTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 21) +public class TodayStepDBHelperTest { + + private static final String SPORT_DATE = "sportDate"; + private static final String STEP_NUM = "stepNum"; + + private ITodayStepDBHelper mTodayStepDBHelper; + + @Before + public void before(){ + + Application application = RuntimeEnvironment.application; + + System.out.println(application); + + mTodayStepDBHelper = TodayStepDBHelper.factory(application); + + } + + @Test + public void insert(){ + + //10天 + for (int i = 0; i<10; i++){ + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(DateUtils.getDateMillis("2018-01-01","yyyy-MM-dd")); + if(4 == i || 5 == i || 7 == i || 8 == i){ + continue; + } + calendar.add(Calendar.DAY_OF_YEAR,i); + //每天存储5次步数 + for (int j = 0; j<5; j++){ + TodayStepData todayStepData = new TodayStepData(); + todayStepData.setToday(DateUtils.dateFormat(calendar.getTimeInMillis(),"yyyy-MM-dd")); + todayStepData.setStep(100*j); + Calendar hourCalendar = Calendar.getInstance(); + hourCalendar.setTimeInMillis(DateUtils.getDateMillis("2018-01-01 08:00","yyyy-MM-dd HH:mm")); + hourCalendar.add(Calendar.HOUR_OF_DAY,j); + todayStepData.setDate(hourCalendar.getTimeInMillis()); + mTodayStepDBHelper.insert(todayStepData); + } + } + + List todayStepDataList = mTodayStepDBHelper.getQueryAll(); + JSONArray jsonArray = getSportStepJsonArray(todayStepDataList); + System.out.println(jsonArray.toString()); + + todayStepDataList = mTodayStepDBHelper.getStepListByDate("2018-01-01"); + jsonArray = getSportStepJsonArray(todayStepDataList); + System.out.println(jsonArray.toString()); + + todayStepDataList = mTodayStepDBHelper.getStepListByStartDateAndDays("2018-01-03",3); + jsonArray = getSportStepJsonArray(todayStepDataList); + System.out.println(jsonArray.toString()); + + + +// mTodayStepDBHelper.clearCapacity("2018-01-10",7); +// +// todayStepDataList = mTodayStepDBHelper.getQueryAll(); +// jsonArray = getSportStepJsonArray(todayStepDataList); +// Systemhttp://pa.mokous.com/share/renewal/esb_plus_award_notice.html.out.println(jsonArray.toString()); + + + } + + + private JSONArray getSportStepJsonArray(List todayStepDataArrayList){ + JSONArray jsonArray = new JSONArray(); + if (null == todayStepDataArrayList || 0 == todayStepDataArrayList.size()) { + return jsonArray; + } + for (int i = 0; i < todayStepDataArrayList.size(); i++) { + TodayStepData todayStepData = todayStepDataArrayList.get(i); + try { + JSONObject subObject = new JSONObject(); + subObject.put(TodayStepDBHelper.TODAY, todayStepData.getToday()); + subObject.put(SPORT_DATE, DateUtils.dateFormat(todayStepData.getDate(),"yyyy-MM-dd HH:mm")); + subObject.put(STEP_NUM, todayStepData.getStep()); + jsonArray.put(subObject); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return jsonArray; + } +}