diff --git a/app/build.gradle b/app/build.gradle index 35a0ef7168..5098dc3e4f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,8 @@ ext { ormLiteVersion = "4.46" powermockVersion = "1.7.3" dexmakerVersion = "1.2" + retrofit2Version = '2.8.1' + okhttp3Version="4.4.1" } @@ -224,9 +226,9 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.google.android.gms:play-services-wearable:17.0.0' implementation "com.google.android.gms:play-services-location:17.0.0" - implementation 'com.google.firebase:firebase-core:17.2.1' - implementation 'com.google.firebase:firebase-auth:19.2.0' - implementation 'com.google.firebase:firebase-database:19.2.0' + implementation 'com.google.firebase:firebase-core:17.2.3' + implementation 'com.google.firebase:firebase-auth:19.3.0' + implementation 'com.google.firebase:firebase-database:19.2.1' implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { transitive = true; } @@ -240,7 +242,7 @@ dependencies { implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.percentlayout:percentlayout:1.0.0' implementation "androidx.preference:preference-ktx:1.1.0" - implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.android.material:material:1.1.0' implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.reactivex.rxjava2:rxandroid:2.1.1" @@ -251,7 +253,7 @@ dependencies { exclude group: "com.google.android", module: "android" } implementation "org.apache.commons:commons-lang3:3.9" - implementation "org.slf4j:slf4j-api:1.7.29" + implementation 'org.slf4j:slf4j-api:1.7.30' // Graphview cannot be upgraded implementation "com.jjoe64:graphview:4.0.1" implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.2.2" @@ -288,7 +290,7 @@ dependencies { testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}" testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}" testImplementation "joda-time:joda-time:2.10.5" - testImplementation("com.google.truth:truth:0.39") { + testImplementation('com.google.truth:truth:1.0.1') { exclude group: "com.google.guava", module: "guava" exclude group: "com.google.code.findbugs", module: "jsr305" } @@ -300,11 +302,11 @@ dependencies { // new for tidepool - implementation 'com.squareup.okhttp3:okhttp:4.2.2' - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' - implementation "com.squareup.retrofit2:retrofit:2.6.2" - implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.2" - implementation "com.squareup.retrofit2:converter-gson:2.6.2" + implementation "com.squareup.okhttp3:okhttp:$okhttp3Version" + implementation "com.squareup.okhttp3:logging-interceptor:$okhttp3Version" + implementation "com.squareup.retrofit2:retrofit:$retrofit2Version" + implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2Version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit2Version" // Phone checker implementation 'com.scottyab:rootbeer-lib:0.0.7' diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java deleted file mode 100644 index b0c09bc44d..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ /dev/null @@ -1,370 +0,0 @@ -package info.nightscout.androidaps; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.PersistableBundle; -import android.text.SpannableString; -import android.text.method.LinkMovementMethod; -import android.text.util.Linkify; -import android.util.TypedValue; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.core.app.ActivityCompat; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.navigation.NavigationView; -import com.google.android.material.tabs.TabLayout; -import com.joanzapata.iconify.Iconify; -import com.joanzapata.iconify.fonts.FontAwesomeModule; - -import javax.inject.Inject; - -import dagger.android.AndroidInjection; -import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; -import info.nightscout.androidaps.activities.PreferencesActivity; -import info.nightscout.androidaps.activities.SingleFragmentActivity; -import info.nightscout.androidaps.activities.StatsActivity; -import info.nightscout.androidaps.events.EventAppExit; -import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.events.EventRebuildTabs; -import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity; -import info.nightscout.androidaps.interfaces.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils; -import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.setupwizard.SetupWizardActivity; -import info.nightscout.androidaps.tabs.TabPageAdapter; -import info.nightscout.androidaps.utils.AndroidPermission; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.LocaleHelper; -import info.nightscout.androidaps.utils.OKDialog; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; -import info.nightscout.androidaps.utils.protection.ProtectionCheck; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; - -import static info.nightscout.androidaps.utils.extensions.EspressoTestHelperKt.isRunningRealPumpTest; - -public class MainActivity extends NoSplashAppCompatActivity { - - private CompositeDisposable disposable = new CompositeDisposable(); - private ActionBarDrawerToggle actionBarDrawerToggle; - private MenuItem pluginPreferencesMenuItem; - - @Inject AAPSLogger aapsLogger; - @Inject RxBusWrapper rxBus; - @Inject AndroidPermission androidPermission; - @Inject SP sp; - @Inject ResourceHelper resourceHelper; - @Inject VersionCheckerUtils versionCheckerUtils; - @Inject SmsCommunicatorPlugin smsCommunicatorPlugin; - @Inject LoopPlugin loopPlugin; - @Inject NSSettingsStatus nsSettingsStatus; - @Inject BuildHelper buildHelper; - @Inject ActivePluginProvider activePlugin; - @Inject FabricPrivacy fabricPrivacy; - @Inject ProtectionCheck protectionCheck; - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Iconify.with(new FontAwesomeModule()); - LocaleHelper.INSTANCE.update(getApplicationContext()); - - setContentView(R.layout.activity_main); - setSupportActionBar(findViewById(R.id.toolbar)); - getSupportActionBar().setDisplayShowTitleEnabled(false); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - - DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); - actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.open_navigation, R.string.close_navigation); - drawerLayout.addDrawerListener(actionBarDrawerToggle); - actionBarDrawerToggle.syncState(); - - // initialize screen wake lock - processPreferenceChange(new EventPreferenceChange(resourceHelper.gs(R.string.key_keep_screen_on))); - - final ViewPager viewPager = findViewById(R.id.pager); - viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { - checkPluginPreferences(viewPager); - } - - @Override - public void onPageScrollStateChanged(int state) { - } - }); - - //Check here if loop plugin is disabled. Else check via constraints - if (!loopPlugin.isEnabled(PluginType.LOOP)) - versionCheckerUtils.triggerCheckVersion(); - - fabricPrivacy.setUserStats(); - - setupTabs(); - setupViews(); - - disposable.add(rxBus - .toObservable(EventRebuildTabs.class) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(event -> { - LocaleHelper.INSTANCE.update(getApplicationContext()); - if (event.getRecreate()) { - recreate(); - } else { - setupTabs(); - setupViews(); - } - setWakeLock(); - }, exception -> fabricPrivacy.logException(exception)) - ); - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::processPreferenceChange, exception -> fabricPrivacy.logException(exception)) - ); - - if (!sp.getBoolean(R.string.key_setupwizard_processed, false) && !isRunningRealPumpTest()) { - Intent intent = new Intent(this, SetupWizardActivity.class); - startActivity(intent); - } - - androidPermission.notifyForStoragePermission(this); - androidPermission.notifyForBatteryOptimizationPermission(this); - if (Config.PUMPDRIVERS) { - androidPermission.notifyForLocationPermissions(this); - androidPermission.notifyForSMSPermissions(this, smsCommunicatorPlugin); - androidPermission.notifyForSystemWindowPermissions(this); - } - } - - private void checkPluginPreferences(ViewPager viewPager) { - if (pluginPreferencesMenuItem == null) return; - if (((TabPageAdapter) viewPager.getAdapter()).getPluginAt(viewPager.getCurrentItem()).getPreferencesId() != -1) - pluginPreferencesMenuItem.setEnabled(true); - else pluginPreferencesMenuItem.setEnabled(false); - } - - @Override - public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { - super.onPostCreate(savedInstanceState, persistentState); - actionBarDrawerToggle.syncState(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - disposable.clear(); - } - - @Override - protected void onResume() { - super.onResume(); - protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, null, this::finish, this::finish); - } - - private void setWakeLock() { - boolean keepScreenOn = sp.getBoolean(R.string.key_keep_screen_on, false); - if (keepScreenOn) - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - else - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - public void processPreferenceChange(final EventPreferenceChange ev) { - if (ev.isChanged(resourceHelper, R.string.key_keep_screen_on)) - setWakeLock(); - } - - private void setupViews() { - TabPageAdapter pageAdapter = new TabPageAdapter(getSupportFragmentManager(), this); - NavigationView navigationView = findViewById(R.id.navigation_view); - navigationView.setNavigationItemSelectedListener(menuItem -> true); - Menu menu = navigationView.getMenu(); - menu.clear(); - for (PluginBase p : activePlugin.getPluginsList()) { - pageAdapter.registerNewFragment(p); - if (p.hasFragment() && !p.isFragmentVisible() && p.isEnabled(p.getPluginDescription().getType()) && !p.getPluginDescription().neverVisible) { - MenuItem menuItem = menu.add(p.getName()); - menuItem.setCheckable(true); - menuItem.setOnMenuItemClickListener(item -> { - Intent intent = new Intent(this, SingleFragmentActivity.class); - intent.putExtra("plugin", activePlugin.getPluginsList().indexOf(p)); - startActivity(intent); - ((DrawerLayout) findViewById(R.id.drawer_layout)).closeDrawers(); - return true; - }); - } - } - ViewPager mPager = findViewById(R.id.pager); - mPager.setAdapter(pageAdapter); - //if (switchToLast) - // mPager.setCurrentItem(pageAdapter.getCount() - 1, false); - checkPluginPreferences(mPager); - } - - private void setupTabs() { - ViewPager viewPager = findViewById(R.id.pager); - TabLayout normalTabs = findViewById(R.id.tabs_normal); - normalTabs.setupWithViewPager(viewPager, true); - TabLayout compactTabs = findViewById(R.id.tabs_compact); - compactTabs.setupWithViewPager(viewPager, true); - Toolbar toolbar = findViewById(R.id.toolbar); - if (sp.getBoolean("short_tabtitles", false)) { - normalTabs.setVisibility(View.GONE); - compactTabs.setVisibility(View.VISIBLE); - toolbar.setLayoutParams(new LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.compact_height))); - } else { - normalTabs.setVisibility(View.VISIBLE); - compactTabs.setVisibility(View.GONE); - TypedValue typedValue = new TypedValue(); - if (getTheme().resolveAttribute(R.attr.actionBarSize, typedValue, true)) { - toolbar.setLayoutParams(new LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT, - TypedValue.complexToDimensionPixelSize(typedValue.data, getResources().getDisplayMetrics()))); - } - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (permissions.length != 0) { - if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) { - switch (requestCode) { - case AndroidPermission.CASE_STORAGE: - //show dialog after permission is granted - OKDialog.show(this, "", resourceHelper.gs(R.string.alert_dialog_storage_permission_text)); - break; - case AndroidPermission.CASE_LOCATION: - case AndroidPermission.CASE_SMS: - case AndroidPermission.CASE_BATTERY: - case AndroidPermission.CASE_PHONE_STATE: - case AndroidPermission.CASE_SYSTEM_WINDOW: - break; - } - } - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - View v = getCurrentFocus(); - if (v instanceof EditText) { - Rect outRect = new Rect(); - v.getGlobalVisibleRect(outRect); - if (!outRect.contains((int) event.getRawX(), (int) event.getRawY())) { - v.clearFocus(); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - } - } - return super.dispatchTouchEvent(event); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_main, menu); - pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences); - checkPluginPreferences(findViewById(R.id.pager)); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - switch (id) { - case R.id.nav_preferences: - protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, () -> { - Intent i = new Intent(this, PreferencesActivity.class); - i.putExtra("id", -1); - startActivity(i); - }); - return true; - case R.id.nav_historybrowser: - startActivity(new Intent(this, HistoryBrowseActivity.class)); - return true; - case R.id.nav_setupwizard: - startActivity(new Intent(this, SetupWizardActivity.class)); - return true; - case R.id.nav_about: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(resourceHelper.gs(R.string.app_name) + " " + BuildConfig.VERSION); - builder.setIcon(resourceHelper.getIcon()); - String message = "Build: " + BuildConfig.BUILDVERSION + "\n"; - message += "Flavor: " + BuildConfig.FLAVOR + BuildConfig.BUILD_TYPE + "\n"; - message += resourceHelper.gs(R.string.configbuilder_nightscoutversion_label) + " " + nsSettingsStatus.getNightscoutVersionName(); - if (buildHelper.isEngineeringMode()) - message += "\n" + resourceHelper.gs(R.string.engineering_mode_enabled); - message += resourceHelper.gs(R.string.about_link_urls); - final SpannableString messageSpanned = new SpannableString(message); - Linkify.addLinks(messageSpanned, Linkify.WEB_URLS); - builder.setMessage(messageSpanned); - builder.setPositiveButton(resourceHelper.gs(R.string.ok), null); - AlertDialog alertDialog = builder.create(); - alertDialog.show(); - ((TextView) alertDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); - return true; - case R.id.nav_exit: - aapsLogger.debug(LTag.CORE, "Exiting"); - rxBus.send(new EventAppExit()); - finish(); - System.runFinalization(); - System.exit(0); - return true; - case R.id.nav_plugin_preferences: - ViewPager viewPager = findViewById(R.id.pager); - final PluginBase plugin = ((TabPageAdapter) viewPager.getAdapter()).getPluginAt(viewPager.getCurrentItem()); - protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, () -> { - Intent i = new Intent(this, PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - startActivity(i); - }); - return true; -/* - case R.id.nav_survey: - startActivity(new Intent(this, SurveyActivity.class)); - return true; -*/ - case R.id.nav_stats: - startActivity(new Intent(this, StatsActivity.class)); - return true; - } - return actionBarDrawerToggle.onOptionsItemSelected(item); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt new file mode 100644 index 0000000000..90cf353d13 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt @@ -0,0 +1,333 @@ +package info.nightscout.androidaps + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Rect +import android.os.Bundle +import android.os.PersistableBundle +import android.text.SpannableString +import android.text.method.LinkMovementMethod +import android.text.util.Linkify +import android.util.TypedValue +import android.view.Menu +import android.view.MenuItem +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.core.app.ActivityCompat +import androidx.drawerlayout.widget.DrawerLayout +import androidx.viewpager.widget.ViewPager +import com.google.android.material.navigation.NavigationView +import com.google.android.material.tabs.TabLayout +import com.joanzapata.iconify.Iconify +import com.joanzapata.iconify.fonts.FontAwesomeModule +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity +import info.nightscout.androidaps.activities.PreferencesActivity +import info.nightscout.androidaps.activities.SingleFragmentActivity +import info.nightscout.androidaps.activities.StatsActivity +import info.nightscout.androidaps.events.EventAppExit +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventRebuildTabs +import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils +import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin +import info.nightscout.androidaps.setupwizard.SetupWizardActivity +import info.nightscout.androidaps.tabs.TabPageAdapter +import info.nightscout.androidaps.utils.AndroidPermission +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.LocaleHelper.update +import info.nightscout.androidaps.utils.OKDialog.show +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.extensions.isRunningRealPumpTest +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.activity_main.* +import javax.inject.Inject +import kotlin.system.exitProcess + +class MainActivity : NoSplashAppCompatActivity() { + private val disposable = CompositeDisposable() + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var androidPermission: AndroidPermission + @Inject lateinit var sp: SP + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var versionCheckerUtils: VersionCheckerUtils + @Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin + @Inject lateinit var loopPlugin: LoopPlugin + @Inject lateinit var nsSettingsStatus: NSSettingsStatus + @Inject lateinit var buildHelper: BuildHelper + @Inject lateinit var activePlugin: ActivePluginProvider + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var protectionCheck: ProtectionCheck + + private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle + private var pluginPreferencesMenuItem: MenuItem? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Iconify.with(FontAwesomeModule()) + update(applicationContext) + setContentView(R.layout.activity_main) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayShowTitleEnabled(false) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setHomeButtonEnabled(true) + actionBarDrawerToggle = ActionBarDrawerToggle(this, drawer_layout, R.string.open_navigation, R.string.close_navigation).also { + drawer_layout.addDrawerListener(it) + it.syncState() + } + + // initialize screen wake lock + processPreferenceChange(EventPreferenceChange(resourceHelper.gs(R.string.key_keep_screen_on))) + pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} + override fun onPageSelected(position: Int) { + checkPluginPreferences(pager) + } + + override fun onPageScrollStateChanged(state: Int) {} + }) + + //Check here if loop plugin is disabled. Else check via constraints + if (!loopPlugin.isEnabled(PluginType.LOOP)) versionCheckerUtils.triggerCheckVersion() + fabricPrivacy.setUserStats() + setupTabs() + setupViews() + disposable.add(rxBus + .toObservable(EventRebuildTabs::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + update(applicationContext) + if (it.recreate) recreate() + else { + setupTabs() + setupViews() + } + setWakeLock() + }) { fabricPrivacy.logException(it) } + ) + disposable.add(rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ processPreferenceChange(it) }) { fabricPrivacy.logException(it) } + ) + if (!sp.getBoolean(R.string.key_setupwizard_processed, false) && !isRunningRealPumpTest()) { + val intent = Intent(this, SetupWizardActivity::class.java) + startActivity(intent) + } + androidPermission.notifyForStoragePermission(this) + androidPermission.notifyForBatteryOptimizationPermission(this) + if (Config.PUMPDRIVERS) { + androidPermission.notifyForLocationPermissions(this) + androidPermission.notifyForSMSPermissions(this, smsCommunicatorPlugin) + androidPermission.notifyForSystemWindowPermissions(this) + } + } + + private fun checkPluginPreferences(viewPager: ViewPager) { + pluginPreferencesMenuItem?.isEnabled = (viewPager.adapter as TabPageAdapter).getPluginAt(viewPager.currentItem).preferencesId != -1 + } + + override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { + super.onPostCreate(savedInstanceState, persistentState) + actionBarDrawerToggle.syncState() + } + + public override fun onDestroy() { + super.onDestroy() + disposable.clear() + } + + override fun onResume() { + super.onResume() + protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, null, Runnable { finish() }, Runnable { finish() }) + } + + private fun setWakeLock() { + val keepScreenOn = sp.getBoolean(R.string.key_keep_screen_on, false) + if (keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private fun processPreferenceChange(ev: EventPreferenceChange) { + if (ev.isChanged(resourceHelper, R.string.key_keep_screen_on)) setWakeLock() + } + + private fun setupViews() { + val pageAdapter = TabPageAdapter(supportFragmentManager, this) + val navigationView = findViewById(R.id.navigation_view) + navigationView.setNavigationItemSelectedListener { true } + val menu = navigationView.menu.also { it.clear() } + for (p in activePlugin.pluginsList) { + pageAdapter.registerNewFragment(p) + if (p.hasFragment() && !p.isFragmentVisible() && p.isEnabled(p.pluginDescription.type) && !p.pluginDescription.neverVisible) { + val menuItem = menu.add(p.name) + menuItem.isCheckable = true + menuItem.setOnMenuItemClickListener { + val intent = Intent(this, SingleFragmentActivity::class.java) + intent.putExtra("plugin", activePlugin.pluginsList.indexOf(p)) + startActivity(intent) + (findViewById(R.id.drawer_layout) as DrawerLayout).closeDrawers() + true + } + } + } + val mPager = findViewById(R.id.pager) + mPager.adapter = pageAdapter + //if (switchToLast) + // mPager.setCurrentItem(pageAdapter.getCount() - 1, false); + checkPluginPreferences(mPager) + } + + private fun setupTabs() { + val viewPager = findViewById(R.id.pager) + val normalTabs = findViewById(R.id.tabs_normal) + normalTabs.setupWithViewPager(viewPager, true) + val compactTabs = findViewById(R.id.tabs_compact) + compactTabs.setupWithViewPager(viewPager, true) + val toolbar = findViewById(R.id.toolbar) + if (sp.getBoolean(R.string.key_short_tabtitles, false)) { + normalTabs.visibility = View.GONE + compactTabs.visibility = View.VISIBLE + toolbar.layoutParams = LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT, resources.getDimension(R.dimen.compact_height).toInt()) + } else { + normalTabs.visibility = View.VISIBLE + compactTabs.visibility = View.GONE + val typedValue = TypedValue() + if (theme.resolveAttribute(R.attr.actionBarSize, typedValue, true)) { + toolbar.layoutParams = LinearLayout.LayoutParams(Toolbar.LayoutParams.MATCH_PARENT, + TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics)) + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (permissions.isNotEmpty()) { + if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) { + when (requestCode) { + AndroidPermission.CASE_STORAGE -> //show dialog after permission is granted + show(this, "", resourceHelper.gs(R.string.alert_dialog_storage_permission_text)) + + AndroidPermission.CASE_LOCATION, AndroidPermission.CASE_SMS, AndroidPermission.CASE_BATTERY, AndroidPermission.CASE_PHONE_STATE, AndroidPermission.CASE_SYSTEM_WINDOW -> { + } + } + } + } + } + + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_DOWN) { + val v = currentFocus + if (v is EditText) { + val outRect = Rect() + v.getGlobalVisibleRect(outRect) + if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) { + v.clearFocus() + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v.getWindowToken(), 0) + } + } + } + return super.dispatchTouchEvent(event) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences) + checkPluginPreferences(findViewById(R.id.pager)) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.nav_preferences -> { + protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(this, PreferencesActivity::class.java) + i.putExtra("id", -1) + startActivity(i) + }) + return true + } + + R.id.nav_historybrowser -> { + startActivity(Intent(this, HistoryBrowseActivity::class.java)) + return true + } + + R.id.nav_setupwizard -> { + startActivity(Intent(this, SetupWizardActivity::class.java)) + return true + } + + R.id.nav_about -> { + var message = "Build: ${BuildConfig.BUILDVERSION}\n" + message += "Flavor: ${BuildConfig.FLAVOR}${BuildConfig.BUILD_TYPE}\n" + message += "${resourceHelper.gs(R.string.configbuilder_nightscoutversion_label)} ${nsSettingsStatus.nightscoutVersionName}" + if (buildHelper.isEngineeringMode()) message += "\n${resourceHelper.gs(R.string.engineering_mode_enabled)}" + message += resourceHelper.gs(R.string.about_link_urls) + val messageSpanned = SpannableString(message) + Linkify.addLinks(messageSpanned, Linkify.WEB_URLS) + AlertDialog.Builder(this) + .setTitle(resourceHelper.gs(R.string.app_name) + " " + BuildConfig.VERSION) + .setIcon(resourceHelper.getIcon()) + .setMessage(messageSpanned) + .setPositiveButton(resourceHelper.gs(R.string.ok), null) + .create().also { + it.show() + (it.findViewById(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance() + } + return true + } + + R.id.nav_exit -> { + aapsLogger.debug(LTag.CORE, "Exiting") + rxBus.send(EventAppExit()) + finish() + System.runFinalization() + exitProcess(0) + } + + R.id.nav_plugin_preferences -> { + val viewPager = findViewById(R.id.pager) + val plugin = (viewPager.adapter as TabPageAdapter).getPluginAt(viewPager.currentItem) + protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(this, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + startActivity(i) + }) + return true + } +/* + R.id.nav_survey -> { + startActivity(Intent(this, SurveyActivity::class.java)) + return true + } +*/ + R.id.nav_stats -> { + startActivity(Intent(this, StatsActivity::class.java)) + return true + } + } + return actionBarDrawerToggle.onOptionsItemSelected(item) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 6270ac2ffb..22b278abba 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -3,6 +3,7 @@ package info.nightscout.androidaps; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -25,6 +26,8 @@ import net.danlew.android.joda.JodaTimeAndroid; import org.json.JSONException; +import java.util.List; + import javax.inject.Inject; import dagger.android.AndroidInjector; @@ -33,65 +36,15 @@ import dagger.android.HasAndroidInjector; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.dependencyInjection.DaggerAppComponent; +import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.PluginStore; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; -import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin; -import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; -import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; -import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin; -import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin; -import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin; import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils; -import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin; -import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; -import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; -import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin; -import info.nightscout.androidaps.plugins.general.food.FoodPlugin; -import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; -import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin; -import info.nightscout.androidaps.plugins.general.wear.WearPlugin; -import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin; -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; -import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; -import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; -import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; -import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; -import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; -import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; -import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin; -import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; -import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.source.DexcomPlugin; -import info.nightscout.androidaps.plugins.source.EversensePlugin; -import info.nightscout.androidaps.plugins.source.GlimpPlugin; -import info.nightscout.androidaps.plugins.source.MM640gPlugin; -import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin; -import info.nightscout.androidaps.plugins.source.PoctechPlugin; -import info.nightscout.androidaps.plugins.source.RandomBgPlugin; -import info.nightscout.androidaps.plugins.source.TomatoPlugin; -import info.nightscout.androidaps.plugins.source.XdripPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.receivers.BTReceiver; import info.nightscout.androidaps.receivers.ChargingStateReceiver; import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.androidaps.receivers.KeepAliveReceiver; @@ -130,60 +83,9 @@ public class MainApp extends DaggerApplication { @Inject SP sp; @Inject ProfileFunction profileFunction; - @Inject ActionsPlugin actionsPlugin; - @Inject AutomationPlugin automationPlugin; - @Inject ComboPlugin comboPlugin; - @Inject CareportalPlugin careportalPlugin; @Inject ConfigBuilderPlugin configBuilderPlugin; - @Inject DanaRPlugin danaRPlugin; - @Inject DanaRSPlugin danaRSPlugin; - @Inject DanaRv2Plugin danaRv2Plugin; - @Inject DanaRKoreanPlugin danaRKoreanPlugin; - @Inject DataBroadcastPlugin dataBroadcastPlugin; - @Inject DstHelperPlugin dstHelperPlugin; - @Inject FoodPlugin foodPlugin; - @Inject InsulinOrefFreePeakPlugin insulinOrefFreePeakPlugin; - @Inject InsulinOrefRapidActingPlugin insulinOrefRapidActingPlugin; - @Inject InsulinOrefUltraRapidActingPlugin insulinOrefUltraRapidActingPlugin; - @Inject IobCobCalculatorPlugin iobCobCalculatorPlugin; - @Inject LocalInsightPlugin localInsightPlugin; - @Inject LocalProfilePlugin localProfilePlugin; - @Inject LoopPlugin loopPlugin; - @Inject MedtronicPumpPlugin medtronicPumpPlugin; - @Inject MDIPlugin mdiPlugin; - @Inject NSProfilePlugin nsProfilePlugin; - @Inject ObjectivesPlugin objectivesPlugin; - @Inject SafetyPlugin safetyPlugin; - @Inject SmsCommunicatorPlugin smsCommunicatorPlugin; - @Inject OpenAPSMAPlugin openAPSMAPlugin; - @Inject OpenAPSAMAPlugin openAPSAMAPlugin; - @Inject OpenAPSSMBPlugin openAPSSMBPlugin; - @Inject OverviewPlugin overviewPlugin; - @Inject PersistentNotificationPlugin persistentNotificationPlugin; - @Inject RandomBgPlugin randomBgPlugin; - @Inject SensitivityOref1Plugin sensitivityOref1Plugin; - @Inject SensitivityAAPSPlugin sensitivityAAPSPlugin; - @Inject SensitivityOref0Plugin sensitivityOref0Plugin; - @Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - @Inject SignatureVerifierPlugin signatureVerifierPlugin; - @Inject StorageConstraintPlugin storageConstraintPlugin; - @Inject DexcomPlugin dexcomPlugin; - @Inject EversensePlugin eversensePlugin; - @Inject GlimpPlugin glimpPlugin; - @Inject MaintenancePlugin maintenancePlugin; - @Inject MM640gPlugin mM640GPlugin; - @Inject NSClientPlugin nsClientPlugin; - @Inject NSClientSourcePlugin nSClientSourcePlugin; - @Inject PoctechPlugin poctechPlugin; - @Inject TomatoPlugin tomatoPlugin; - @Inject XdripPlugin xdripPlugin; - @Inject StatusLinePlugin statusLinePlugin; - @Inject TidepoolPlugin tidepoolPlugin; - @Inject TreatmentsPlugin treatmentsPlugin; - @Inject VirtualPumpPlugin virtualPumpPlugin; - @Inject VersionCheckerPlugin versionCheckerPlugin; - @Inject WearPlugin wearPlugin; @Inject KeepAliveReceiver.KeepAliveManager keepAliveManager; + @Inject List plugins; @Override public void onCreate() { @@ -196,15 +98,13 @@ public class MainApp extends DaggerApplication { generateEmptyNotification(); sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class); -/* TODO: put back Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { if (ex instanceof InternalError) { // usually the app trying to spawn a thread while being killed return; } - log.error("Uncaught exception crashing app", ex); + aapsLogger.error("Uncaught exception crashing app", ex); }); -*/ try { if (fabricPrivacy.fabricEnabled()) { @@ -231,62 +131,7 @@ public class MainApp extends DaggerApplication { versionCheckersUtils.triggerCheckVersion(); // Register all tabs in app here - pluginStore.add(overviewPlugin); - pluginStore.add(iobCobCalculatorPlugin); - if (!Config.NSCLIENT) pluginStore.add(actionsPlugin); - pluginStore.add(insulinOrefRapidActingPlugin); - pluginStore.add(insulinOrefUltraRapidActingPlugin); - pluginStore.add(insulinOrefFreePeakPlugin); - pluginStore.add(sensitivityOref0Plugin); - pluginStore.add(sensitivityAAPSPlugin); - pluginStore.add(sensitivityWeightedAveragePlugin); - pluginStore.add(sensitivityOref1Plugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRKoreanPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRv2Plugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRSPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(localInsightPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(comboPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(medtronicPumpPlugin); - if (!Config.NSCLIENT) pluginStore.add(mdiPlugin); - if (!Config.NSCLIENT) pluginStore.add(virtualPumpPlugin); - if (Config.NSCLIENT) pluginStore.add(careportalPlugin); - if (Config.APS) pluginStore.add(loopPlugin); - if (Config.APS) pluginStore.add(openAPSMAPlugin); - if (Config.APS) pluginStore.add(openAPSAMAPlugin); - if (Config.APS) pluginStore.add(openAPSSMBPlugin); - pluginStore.add(nsProfilePlugin); - if (!Config.NSCLIENT) pluginStore.add(localProfilePlugin); - pluginStore.add(treatmentsPlugin); - if (!Config.NSCLIENT) pluginStore.add(safetyPlugin); - if (!Config.NSCLIENT) pluginStore.add(versionCheckerPlugin); - if (Config.APS) pluginStore.add(storageConstraintPlugin); - if (Config.APS) pluginStore.add(signatureVerifierPlugin); - if (Config.APS) pluginStore.add(objectivesPlugin); - pluginStore.add(xdripPlugin); - pluginStore.add(nSClientSourcePlugin); - pluginStore.add(mM640GPlugin); - pluginStore.add(glimpPlugin); - pluginStore.add(dexcomPlugin); - pluginStore.add(poctechPlugin); - pluginStore.add(tomatoPlugin); - pluginStore.add(eversensePlugin); - pluginStore.add(randomBgPlugin); - if (!Config.NSCLIENT) pluginStore.add(smsCommunicatorPlugin); - pluginStore.add(foodPlugin); - - pluginStore.add(wearPlugin); - pluginStore.add(statusLinePlugin); - pluginStore.add(persistentNotificationPlugin); - pluginStore.add(nsClientPlugin); -// if (engineeringMode) pluginsList.add(tidepoolPlugin); - pluginStore.add(maintenancePlugin); - pluginStore.add(automationPlugin); - pluginStore.add(dstHelperPlugin); - pluginStore.add(dataBroadcastPlugin); - - pluginStore.add(configBuilderPlugin); - + pluginStore.setPlugins(plugins); configBuilderPlugin.initialize(); NSUpload.uploadAppStart(); @@ -356,6 +201,11 @@ public class MainApp extends DaggerApplication { filter.addAction(Intent.ACTION_POWER_DISCONNECTED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(new ChargingStateReceiver(), filter); + + filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + registerReceiver(new BTReceiver(), filter); } @Deprecated diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt index a7e15ea2fa..187ab7b33d 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt @@ -285,8 +285,15 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang } } + val hmacPasswords = arrayOf( + resourceHelper.gs(R.string.key_bolus_password), + resourceHelper.gs(R.string.key_master_password), + resourceHelper.gs(R.string.key_application_password), + resourceHelper.gs(R.string.key_settings_password) + ) + if (pref is Preference) { - if ((pref.key != null) && (pref.key.contains("_password"))) { + if ((pref.key != null) && (hmacPasswords.contains(pref.key))) { if (sp.getString(pref.key, "").startsWith("hmac:")) { pref.summary = "******" } else { diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt index 421a983563..68c95efe66 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt @@ -95,6 +95,7 @@ interface AppComponent : AndroidInjector { fun injectTrigger(triggerAutosensValue: TriggerAutosensValue) fun injectTrigger(triggerBg: TriggerBg) fun injectTrigger(triggerBolusAgo: TriggerBolusAgo) + fun injectTrigger(triggerBTDevice: TriggerBTDevice) fun injectTrigger(triggerCOB: TriggerCOB) fun injectTrigger(triggerConnector: TriggerConnector) fun injectTrigger(triggerDelta: TriggerDelta) @@ -127,8 +128,10 @@ interface AppComponent : AndroidInjector { fun injectElement(inputButton: InputButton) fun injectElement(comparator: Comparator) fun injectElement(comparatorExists: ComparatorExists) + fun injectElement(comparatorConnect: ComparatorConnect) fun injectElement(inputDateTime: InputDateTime) fun injectElement(inputDelta: InputDelta) + fun injectElement(inputDropdownMenu: InputDropdownMenu) fun injectElement(inputDouble: InputDouble) fun injectElement(inputDuration: InputDuration) fun injectElement(inputInsulin: InputInsulin) diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt index 28429bf870..933873bc5e 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt @@ -3,10 +3,12 @@ package info.nightscout.androidaps.dependencyInjection import android.content.Context import androidx.preference.PreferenceManager import dagger.Binds +import dagger.Lazy import dagger.Module import dagger.Provides import dagger.android.ContributesAndroidInjector import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Config import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.data.Profile import info.nightscout.androidaps.data.ProfileStore @@ -15,6 +17,7 @@ import info.nightscout.androidaps.db.BgReading import info.nightscout.androidaps.db.ProfileSwitch import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider +import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLoggerProduction import info.nightscout.androidaps.plugins.aps.loop.APSResult @@ -23,7 +26,6 @@ import info.nightscout.androidaps.plugins.aps.openAPSMA.DetermineBasalResultMA import info.nightscout.androidaps.plugins.aps.openAPSMA.LoggerCallback import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation @@ -33,6 +35,9 @@ import info.nightscout.androidaps.plugins.general.automation.actions.* import info.nightscout.androidaps.plugins.general.automation.elements.* import info.nightscout.androidaps.plugins.general.automation.triggers.* import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData +import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs +import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat +import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction import info.nightscout.androidaps.plugins.general.smsCommunicator.AuthRequest import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData @@ -42,7 +47,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread import info.nightscout.androidaps.plugins.treatments.Treatment import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.queue.commands.* -import info.nightscout.androidaps.setupwizard.SWDefinition import info.nightscout.androidaps.setupwizard.SWEventListener import info.nightscout.androidaps.setupwizard.SWScreen import info.nightscout.androidaps.setupwizard.elements.* @@ -51,11 +55,13 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelperImplementation import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SPImplementation +import info.nightscout.androidaps.utils.storage.FileStorage +import info.nightscout.androidaps.utils.storage.Storage import info.nightscout.androidaps.utils.wizard.BolusWizard import info.nightscout.androidaps.utils.wizard.QuickWizardEntry import javax.inject.Singleton -@Module(includes = [AppModule.AppBindings::class]) +@Module(includes = [AppModule.AppBindings::class, PluginsModule::class]) open class AppModule { @Provides @@ -88,6 +94,24 @@ open class AppModule { */ } + @Provides + fun providesPlugins(@PluginsModule.AllConfigs allConfigs: Map<@JvmSuppressWildcards Int, @JvmSuppressWildcards PluginBase>, + @PluginsModule.PumpDriver pumpDrivers: Lazy>, + @PluginsModule.NotNSClient notNsClient: Lazy>, + @PluginsModule.APS aps: Lazy>): List<@JvmSuppressWildcards PluginBase> { + val plugins = allConfigs.toMutableMap() + if (Config.PUMPDRIVERS) plugins += pumpDrivers.get() + if (Config.APS) plugins += aps.get() + if (!Config.NSCLIENT) plugins += notNsClient.get() + return plugins.toList().sortedBy { it.first }.map { it.second } + } + + @Provides + @Singleton + fun provideStorage(): Storage { + return FileStorage() + } + @Module interface AppBindings { @@ -156,6 +180,7 @@ open class AppModule { @ContributesAndroidInjector fun triggerPumpLastConnectionInjector(): TriggerPumpLastConnection + @ContributesAndroidInjector fun triggerBTDeviceInjector(): TriggerBTDevice @ContributesAndroidInjector fun triggerRecurringTimeInjector(): TriggerRecurringTime @ContributesAndroidInjector fun triggerTempTargetInjector(): TriggerTempTarget @ContributesAndroidInjector fun triggerTime(): TriggerTime @@ -182,10 +207,12 @@ open class AppModule { @ContributesAndroidInjector fun inputBgInjector(): InputBg @ContributesAndroidInjector fun inputButtonInjector(): InputButton @ContributesAndroidInjector fun comparatorInjector(): Comparator + @ContributesAndroidInjector fun comparatorConnectInjector(): ComparatorConnect @ContributesAndroidInjector fun comparatorExistsInjector(): ComparatorExists @ContributesAndroidInjector fun inputDateTimeInjector(): InputDateTime @ContributesAndroidInjector fun inputDeltaInjector(): InputDelta @ContributesAndroidInjector fun inputDoubleInjector(): InputDouble + @ContributesAndroidInjector fun inputDropdownMenuInjector(): InputDropdownMenu @ContributesAndroidInjector fun inputDurationInjector(): InputDuration @ContributesAndroidInjector fun inputInsulinInjector(): InputInsulin @ContributesAndroidInjector fun inputLocationModeInjector(): InputLocationMode @@ -234,6 +261,10 @@ open class AppModule { @ContributesAndroidInjector fun graphDataInjector(): GraphData + @ContributesAndroidInjector fun importExportPrefsInjector(): ImportExportPrefs + @ContributesAndroidInjector fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat + @ContributesAndroidInjector fun classicPrefsFormatInjector(): ClassicPrefsFormat + @Binds fun bindContext(mainApp: MainApp): Context @Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt index 7b9a59cbbc..581c8b84bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt @@ -41,6 +41,7 @@ import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment import info.nightscout.androidaps.plugins.source.BGSourceFragment import info.nightscout.androidaps.plugins.treatments.TreatmentsFragment import info.nightscout.androidaps.plugins.treatments.fragments.* +import info.nightscout.androidaps.utils.protection.PasswordCheck @Module @Suppress("unused") @@ -114,4 +115,6 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesTreatmentDialog(): TreatmentDialog @ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog @ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog + + @ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt new file mode 100644 index 0000000000..928fed610c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt @@ -0,0 +1,379 @@ +package info.nightscout.androidaps.dependencyInjection + +import dagger.Binds +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntKey +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet +import info.nightscout.androidaps.Config +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin +import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin +import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin +import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin +import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin +import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin +import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin +import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin +import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin +import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin +import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin +import info.nightscout.androidaps.plugins.general.food.FoodPlugin +import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin +import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin +import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin +import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin +import info.nightscout.androidaps.plugins.general.wear.WearPlugin +import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin +import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin +import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin +import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin +import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin +import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin +import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin +import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.source.* +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import javax.inject.Qualifier + +@Module +abstract class PluginsModule { + + @Binds + @AllConfigs + @IntoMap + @IntKey(0) + abstract fun bindOverviewPlugin(plugin: OverviewPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(10) + abstract fun bindIobCobCalculatorPlugin(plugin: IobCobCalculatorPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(20) + abstract fun bindActionsPlugin(plugin: ActionsPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(30) + abstract fun bindInsulinOrefRapidActingPlugin(plugin: InsulinOrefRapidActingPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(40) + abstract fun bindInsulinOrefUltraRapidActingPlugin(plugin: InsulinOrefUltraRapidActingPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(50) + abstract fun bindInsulinOrefFreePeakPlugin(plugin: InsulinOrefFreePeakPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(60) + abstract fun bindSensitivityOref0Plugin(plugin: SensitivityOref0Plugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(70) + abstract fun bindSensitivityAAPSPlugin(plugin: SensitivityAAPSPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(80) + abstract fun bindSensitivityWeightedAveragePlugin(plugin: SensitivityWeightedAveragePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(90) + abstract fun bindSensitivityOref1Plugin(plugin: SensitivityOref1Plugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(100) + abstract fun bindDanaRPlugin(plugin: DanaRPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(110) + abstract fun bindDanaRKoreanPlugin(plugin: DanaRKoreanPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(120) + abstract fun bindDanaRv2Plugin(plugin: DanaRv2Plugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(130) + abstract fun bindDanaRSPlugin(plugin: DanaRSPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(140) + abstract fun bindLocalInsightPlugin(plugin: LocalInsightPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(150) + abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(160) + abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(170) + abstract fun bindMDIPlugin(plugin: MDIPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(180) + abstract fun bindVirtualPumpPlugin(plugin: VirtualPumpPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(190) + abstract fun bindCareportalPlugin(plugin: CareportalPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(200) + abstract fun bindLoopPlugin(plugin: LoopPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(210) + abstract fun bindOpenAPSMAPlugin(plugin: OpenAPSMAPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(220) + abstract fun bindOpenAPSAMAPlugin(plugin: OpenAPSAMAPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(230) + abstract fun bindOpenAPSSMBPlugin(plugin: OpenAPSSMBPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(240) + abstract fun bindNSProfilePlugin(plugin: NSProfilePlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(250) + abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(255) + abstract fun bindAutomationPlugin(plugin: AutomationPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(260) + abstract fun bindTreatmentsPlugin(plugin: TreatmentsPlugin): PluginBase + + @Binds + @NotNSClient + @IntoSet + abstract fun bindSafetyPlugin(plugin: SafetyPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(270) + abstract fun bindVersionCheckerPlugin(plugin: VersionCheckerPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(280) + abstract fun bindSmsCommunicatorPlugin(plugin: SmsCommunicatorPlugin): PluginBase + + + @Binds + @APS + @IntoMap + @IntKey(290) + abstract fun bindStorageConstraintPlugin(plugin: StorageConstraintPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(300) + abstract fun bindSignatureVerifierPlugin(plugin: SignatureVerifierPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(310) + abstract fun bindObjectivesPlugin(plugin: ObjectivesPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(320) + abstract fun bindFoodPlugin(plugin: FoodPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(330) + abstract fun bindWearPlugin(plugin: WearPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(340) + abstract fun bindStatusLinePlugin(plugin: StatusLinePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(350) + abstract fun bindPersistentNotificationPlugin(plugin: PersistentNotificationPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(360) + abstract fun bindNSClientPlugin(plugin: NSClientPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(370) + abstract fun bindMaintenancePlugin(plugin: MaintenancePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(380) + abstract fun bindDstHelperPlugin(plugin: DstHelperPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(390) + abstract fun bindDataBroadcastPlugin(plugin: DataBroadcastPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(400) + abstract fun bindXdripPlugin(plugin: XdripPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(410) + abstract fun bindNSClientSourcePlugin(plugin: NSClientSourcePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(420) + abstract fun bindMM640gPlugin(plugin: MM640gPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(430) + abstract fun bindGlimpPlugin(plugin: GlimpPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(440) + abstract fun bindDexcomPlugin(plugin: DexcomPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(450) + abstract fun bindPoctechPlugin(plugin: PoctechPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(460) + abstract fun bindTomatoPlugin(plugin: TomatoPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(470) + abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(480) + abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase + + @Qualifier + annotation class AllConfigs + + @Qualifier + annotation class PumpDriver + + @Qualifier + annotation class NotNSClient + + @Qualifier + annotation class APS + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt index 6c6890fa74..85e4850391 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt @@ -3,16 +3,13 @@ package info.nightscout.androidaps.dependencyInjection import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver -import info.nightscout.androidaps.receivers.ChargingStateReceiver -import info.nightscout.androidaps.receivers.DataReceiver -import info.nightscout.androidaps.receivers.KeepAliveReceiver -import info.nightscout.androidaps.receivers.NetworkChangeReceiver -import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver +import info.nightscout.androidaps.receivers.* @Module @Suppress("unused") abstract class ReceiversModule { + @ContributesAndroidInjector abstract fun contributesBTReceiver(): BTReceiver @ContributesAndroidInjector abstract fun contributesChargingStateReceiver(): ChargingStateReceiver @ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver @ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventBTChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventBTChange.kt new file mode 100644 index 0000000000..0531a4d00d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventBTChange.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.events + +class EventBTChange constructor(val state: Change, val deviceName: String) : Event() { + enum class Change { + CONNECT, + DISCONNECT + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java index 3c081e3309..6a47e3749b 100644 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java @@ -303,7 +303,7 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity { // add basal data if (pump.getPumpDescription().isTempBasalCapable && showBasal) { - graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2d); + graphData.addBasals(fromTime, toTime, lowLine / graphData.getMaxY() / 1.2d); } // **** NOW line **** diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt index 0bce3e34eb..31692b93bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt @@ -6,6 +6,7 @@ import info.nightscout.androidaps.logging.LTag import java.util.* import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList @Singleton class PluginStore @Inject constructor( @@ -26,8 +27,7 @@ class PluginStore @Inject constructor( return pluginStore!! } } - - var plugins = ArrayList() + lateinit var plugins: List<@JvmSuppressWildcards PluginBase> private var activeBgSource: BgSourceInterface? = null private var activePump: PumpInterface? = null @@ -41,11 +41,6 @@ class PluginStore @Inject constructor( verifySelectionInCategories() } - fun add(pluginBase: PluginBase): ActivePluginProvider { - plugins.add(pluginBase) - return this - } - fun getDefaultPlugin(type: PluginType): PluginBase { for (p in plugins) if (p.getType() == type && p.isDefault()) return p @@ -243,6 +238,6 @@ class PluginStore @Inject constructor( override fun getActiveTreatments(): TreatmentsInterface = activeTreatments ?: checkNotNull(activeTreatments) { "No treatments selected" } - override fun getPluginsList(): ArrayList = plugins + override fun getPluginsList(): ArrayList = ArrayList(plugins) } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt index 9052055573..6e1aad8871 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt @@ -6,6 +6,7 @@ import android.os.Build import android.os.Handler import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventBTChange import info.nightscout.androidaps.events.EventChargingState import info.nightscout.androidaps.events.EventLocationChange import info.nightscout.androidaps.events.EventNetworkChange @@ -38,6 +39,7 @@ import org.json.JSONObject import java.util.* import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList @Singleton class AutomationPlugin @Inject constructor( @@ -65,6 +67,7 @@ class AutomationPlugin @Inject constructor( val automationEvents = ArrayList() var executionLog: MutableList = ArrayList() + var btConnects : MutableList = ArrayList() private val loopHandler = Handler() private lateinit var refreshLoop: Runnable @@ -123,6 +126,14 @@ class AutomationPlugin @Inject constructor( .toObservable(EventAutosensCalculationFinished::class.java) .observeOn(Schedulers.io()) .subscribe({ processActions() }, { fabricPrivacy.logException(it) }) + disposable += rxBus + .toObservable(EventBTChange::class.java) + .observeOn(Schedulers.io()) + .subscribe({ + aapsLogger.debug(LTag.AUTOMATION, "Grabbed new BT event: $it") + btConnects.add(it) + processActions() + }, { fabricPrivacy.logException(it) }) } override fun onStop() { @@ -197,6 +208,12 @@ class AutomationPlugin @Inject constructor( event.lastRun = DateUtil.now() } } + // we cannot detect connected BT devices + // so let's collect all connection/disconnections between 2 runs of processActions() + // TriggerBTDevice can pick up and process these events + // after processing clear events to prevent repeated actions + btConnects.clear() + storeToSP() // save last run time } @@ -231,8 +248,8 @@ class AutomationPlugin @Inject constructor( TriggerLocation(injector), TriggerAutosensValue(injector), TriggerBolusAgo(injector), - TriggerPumpLastConnection(injector) + TriggerPumpLastConnection(injector), + TriggerBTDevice(injector) ) } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.kt index 8260f341e2..76152b8861 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.kt @@ -13,6 +13,7 @@ import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuil import info.nightscout.androidaps.plugins.general.nsclient.NSUpload import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationUserMessage import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.utils.JsonHelper import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -30,7 +31,7 @@ class ActionNotification(injector: HasAndroidInjector) : Action(injector) { @DrawableRes override fun icon(): Int = R.drawable.ic_notifications override fun doAction(callback: Callback) { - val notification = Notification(Notification.USERMESSAGE, text.value, Notification.URGENT) + val notification = NotificationUserMessage(text.value) rxBus.send(EventNewNotification(notification)) NSUpload.uploadError(text.value) rxBus.send(EventRefreshOverview("ActionNotification")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/ComparatorConnect.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/ComparatorConnect.kt new file mode 100644 index 0000000000..3e293fef5a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/ComparatorConnect.kt @@ -0,0 +1,60 @@ +package info.nightscout.androidaps.plugins.general.automation.elements + +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.LinearLayout +import android.widget.Spinner +import androidx.annotation.StringRes +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.* +import javax.inject.Inject + +class ComparatorConnect(injector: HasAndroidInjector) : Element(injector) { + @Inject lateinit var resourceHelper: ResourceHelper + + enum class Compare { + ON_CONNECT, ON_DISCONNECT; + + @get:StringRes val stringRes: Int + get() = when (this) { + ON_CONNECT -> R.string.onconnect + ON_DISCONNECT -> R.string.ondisconnect + } + + companion object { + fun labels(resourceHelper: ResourceHelper): List { + val list: MutableList = ArrayList() + for (c in values()) list.add(resourceHelper.gs(c.stringRes)) + return list + } + } + } + + constructor(injector: HasAndroidInjector, value: Compare) : this(injector) { + this.value = value + } + + var value = Compare.ON_CONNECT + + override fun addToLayout(root: LinearLayout) { + val spinner = Spinner(root.context) + val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper)) + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + spinner.adapter = spinnerArrayAdapter + val spinnerParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4)) + spinner.layoutParams = spinnerParams + spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { + value = Compare.values()[position] + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + spinner.setSelection(value.ordinal) + root.addView(spinner) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDropdownMenu.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDropdownMenu.kt new file mode 100644 index 0000000000..7f894293f9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDropdownMenu.kt @@ -0,0 +1,68 @@ +package info.nightscout.androidaps.plugins.general.automation.elements + +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.LinearLayout +import android.widget.Spinner +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.* +import javax.inject.Inject + +class InputDropdownMenu(injector: HasAndroidInjector) : Element(injector) { + @Inject lateinit var resourceHelper: ResourceHelper + + private var itemList: ArrayList = ArrayList() + var value: String = "" + + constructor(injector: HasAndroidInjector, name: String) : this(injector) { + value = name + } + + constructor(injector: HasAndroidInjector, another: InputDropdownMenu) : this(injector) { + value = another.value + } + + override fun addToLayout(root: LinearLayout) { + val spinner = Spinner(root.context) + spinner.adapter = ArrayAdapter(root.context, + R.layout.spinner_centered, itemList).also { + it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + spinner.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).also { it.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4)) } + + spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { + setValue(itemList[position].toString()) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + spinner.setSelection(0) + root.addView(LinearLayout(root.context).also { + it.orientation = LinearLayout.VERTICAL + it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + it.addView(spinner) + }) + } + + fun setValue(name: String): InputDropdownMenu { + value = name + return this + } + + fun setList(values: ArrayList) { + itemList = ArrayList(values) + } + + // For testing only + fun add(item: String) { + itemList.add(item) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.kt new file mode 100644 index 0000000000..6dfb45be67 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.kt @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.plugins.general.automation.triggers + +import android.bluetooth.BluetoothAdapter +import android.content.Context +import android.widget.LinearLayout +import com.google.common.base.Optional +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventBTChange +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin +import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorConnect +import info.nightscout.androidaps.plugins.general.automation.elements.InputDropdownMenu +import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder +import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel +import info.nightscout.androidaps.utils.JsonHelper +import org.json.JSONObject +import java.util.* +import javax.inject.Inject + +class TriggerBTDevice(injector: HasAndroidInjector) : Trigger(injector) { + @Inject lateinit var context: Context + @Inject lateinit var automationPlugin: AutomationPlugin + + var btDevice = InputDropdownMenu(injector, "") + var comparator: ComparatorConnect = ComparatorConnect(injector) + + private constructor(injector: HasAndroidInjector, triggerBTDevice: TriggerBTDevice) : this(injector) { + comparator = ComparatorConnect(injector, triggerBTDevice.comparator.value) + btDevice.value = triggerBTDevice.btDevice.value + } + + @Synchronized + override fun shouldRun(): Boolean { + if (eventExists()) { + aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription()) + return true + } + return false + } + + @Synchronized override fun toJSON(): String { + val data = JSONObject() + .put("comparator", comparator.value.toString()) + .put("name", btDevice.value) + return JSONObject() + .put("type", this::class.java.name) + .put("data", data) + .toString() + } + + override fun fromJSON(data: String): Trigger { + val d = JSONObject(data) + btDevice.value = JsonHelper.safeGetString(d, "name")!! + comparator.value = ComparatorConnect.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")!!) + return this + } + + override fun friendlyName(): Int = R.string.btdevice + + override fun friendlyDescription(): String = + resourceHelper.gs(R.string.btdevicecompared, btDevice.value, resourceHelper.gs(comparator.value.stringRes)) + + override fun icon(): Optional = Optional.of(R.drawable.ic_bluetooth_white_48dp) + + override fun duplicate(): Trigger = TriggerBTDevice(injector, this) + + override fun generateDialog(root: LinearLayout) { + val pairedDevices = devicesPaired() + btDevice.setList(pairedDevices) + LayoutBuilder() + .add(StaticLabel(injector, R.string.btdevice, this)) + .add(btDevice) + .add(comparator) + .build(root) + } + + // Get the list of paired BT devices to use in dropdown menu + private fun devicesPaired(): ArrayList { + val s = ArrayList() + BluetoothAdapter.getDefaultAdapter()?.bondedDevices?.forEach { s.add(it.name) } + return s + } + + private fun eventExists(): Boolean { + automationPlugin.btConnects.forEach { + if (btDevice.value == it.deviceName) { + if (comparator.value == ComparatorConnect.Compare.ON_CONNECT && it.state == EventBTChange.Change.CONNECT) return true + if (comparator.value == ComparatorConnect.Compare.ON_DISCONNECT && it.state == EventBTChange.Change.DISCONNECT) return true + } + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java deleted file mode 100644 index 59c8e87144..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java +++ /dev/null @@ -1,129 +0,0 @@ -package info.nightscout.androidaps.plugins.general.maintenance; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Environment; -import androidx.preference.PreferenceManager; - -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Map; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventAppExit; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; -import info.nightscout.androidaps.plugins.bus.RxBus; -import info.nightscout.androidaps.utils.OKDialog; -import info.nightscout.androidaps.utils.SP; -import info.nightscout.androidaps.utils.ToastUtils; - -/** - * Created by mike on 03.07.2016. - */ - -public class ImportExportPrefs { - private static Logger log = StacktraceLoggerWrapper.getLogger(L.CORE); - private static File path = new File(Environment.getExternalStorageDirectory().toString()); - static public final File file = new File(path, MainApp.gs(R.string.app_name) + "Preferences"); - - private static final int REQUEST_EXTERNAL_STORAGE = 1; - private static String[] PERMISSIONS_STORAGE = { - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - }; - - static void verifyStoragePermissions(Fragment fragment) { - int permission = ContextCompat.checkSelfPermission(fragment.getContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE); - - if (permission != PackageManager.PERMISSION_GRANTED) { - // We don't have permission so prompt the user - fragment.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); - } - - } - - static void exportSharedPreferences(final Fragment f) { - exportSharedPreferences(f.getContext()); - } - - private static void exportSharedPreferences(final Context context) { - OKDialog.showConfirmation(context, MainApp.gs(R.string.maintenance), MainApp.gs(R.string.export_to) + " " + file + " ?", () -> { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - try { - FileWriter fw = new FileWriter(file); - PrintWriter pw = new PrintWriter(fw); - Map prefsMap = prefs.getAll(); - for (Map.Entry entry : prefsMap.entrySet()) { - pw.println(entry.getKey() + "::" + entry.getValue().toString()); - } - pw.close(); - fw.close(); - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.exported)); - } catch (FileNotFoundException e) { - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.filenotfound) + " " + file); - log.error("Unhandled exception", e); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - }); - } - - static void importSharedPreferences(final Fragment fragment) { - importSharedPreferences(fragment.getContext()); - } - - public static void importSharedPreferences(final Context context) { - OKDialog.showConfirmation(context, MainApp.gs(R.string.maintenance), MainApp.gs(R.string.import_from) + " " + file + " ?", () -> { - String line; - String[] lineParts; - try { - SP.clear(); - - BufferedReader reader = new BufferedReader(new FileReader(file)); - while ((line = reader.readLine()) != null) { - lineParts = line.split("::"); - if (lineParts.length == 2) { - if (lineParts[1].equals("true") || lineParts[1].equals("false")) { - SP.putBoolean(lineParts[0], Boolean.parseBoolean(lineParts[1])); - } else { - SP.putString(lineParts[0], lineParts[1]); - } - } - } - reader.close(); - SP.putBoolean(R.string.key_setupwizard_processed, true); - OKDialog.show(context, MainApp.gs(R.string.setting_imported), MainApp.gs(R.string.restartingapp), () -> { - log.debug("Exiting"); - RxBus.Companion.getINSTANCE().send(new EventAppExit()); - if (context instanceof Activity) { - ((Activity) context).finish(); - } - System.runFinalization(); - System.exit(0); - }); - } catch (FileNotFoundException e) { - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.filenotfound) + " " + file); - log.error("Unhandled exception", e); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - }); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt new file mode 100644 index 0000000000..356fc918e1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt @@ -0,0 +1,340 @@ +package info.nightscout.androidaps.plugins.general.maintenance + +import android.Manifest +import android.app.Activity +import android.bluetooth.BluetoothAdapter +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.os.Environment +import android.provider.Settings +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.PreferencesActivity +import info.nightscout.androidaps.events.EventAppExit +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.maintenance.formats.* +import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePassword +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.OKDialog.show +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.alertDialogs.PrefImportSummaryDialog +import info.nightscout.androidaps.utils.alertDialogs.TwoMessagesAlertDialog +import info.nightscout.androidaps.utils.alertDialogs.WarningDialog +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.protection.PasswordCheck +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.joda.time.DateTime +import org.joda.time.Days +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Created by mike on 03.07.2016. + */ + +private const val REQUEST_EXTERNAL_STORAGE = 1 +private val PERMISSIONS_STORAGE = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE +) + +private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60 + +@Singleton +class ImportExportPrefs @Inject constructor( + private var log: AAPSLogger, + private val resourceHelper: ResourceHelper, + private val sp: SP, + private val buildHelper: BuildHelper, + private val otp: OneTimePassword, + private val rxBus: RxBusWrapper, + private val passwordCheck: PasswordCheck, + private val classicPrefsFormat: ClassicPrefsFormat, + private val encryptedPrefsFormat: EncryptedPrefsFormat +) { + + val TAG = LTag.CORE + + private val path = File(Environment.getExternalStorageDirectory().toString()) + + private val file = File(path, resourceHelper.gs(R.string.app_name) + "Preferences") + private val encFile = File(path, resourceHelper.gs(R.string.app_name) + "Preferences.json") + + fun prefsImportFile(): File { + return if (encFile.exists()) encFile else file + } + + fun prefsFileExists(): Boolean { + return encFile.exists() || file.exists() + } + + + fun exportSharedPreferences(f: Fragment) { + f.activity?.let { exportSharedPreferences(it) } + } + + fun verifyStoragePermissions(fragment: Fragment) { + fragment.context?.let { + val permission = ContextCompat.checkSelfPermission(it, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + fragment.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE) + } + } + } + + private fun prepareMetadata(context: Context): Map { + + val metadata: MutableMap = mutableMapOf() + + metadata[PrefsMetadataKey.DEVICE_NAME] = PrefMetadata(detectUserName(context), PrefsStatus.OK) + metadata[PrefsMetadataKey.CREATED_AT] = PrefMetadata(DateUtil.toISOString(Date()), PrefsStatus.OK) + metadata[PrefsMetadataKey.AAPS_VERSION] = PrefMetadata(BuildConfig.VERSION_NAME, PrefsStatus.OK) + metadata[PrefsMetadataKey.AAPS_FLAVOUR] = PrefMetadata(BuildConfig.FLAVOR, PrefsStatus.OK) + metadata[PrefsMetadataKey.DEVICE_MODEL] = PrefMetadata(getCurrentDeviceModelString(), PrefsStatus.OK) + + if (prefsEncryptionIsDisabled()) { + metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata("Disabled", PrefsStatus.DISABLED) + } else { + metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata("Enabled", PrefsStatus.OK) + } + + return metadata + } + + private fun detectUserName(context: Context): String { + // based on https://medium.com/@pribble88/how-to-get-an-android-device-nickname-4b4700b3068c + val n1 = Settings.System.getString(context.contentResolver, "bluetooth_name") + val n2 = Settings.Secure.getString(context.contentResolver, "bluetooth_name") + val n3 = BluetoothAdapter.getDefaultAdapter()?.name + val n4 = Settings.System.getString(context.contentResolver, "device_name") + val n5 = Settings.Secure.getString(context.contentResolver, "lock_screen_owner_info") + val n6 = Settings.Global.getString(context.contentResolver, "device_name") + + // name we use for SMS OTP token in communicator + val otpName = otp.name().trim() + val defaultOtpName = resourceHelper.gs(R.string.smscommunicator_default_user_display_name) + + // name we detect from OS + val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultOtpName + val name = if (otpName.length > 0 && otpName != defaultOtpName) otpName else systemName + return name + } + + private fun getCurrentDeviceModelString() = + Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")" + + private fun prefsEncryptionIsDisabled() = + buildHelper.isEngineeringMode() && !sp.getBoolean(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs), true) + + private fun askForMasterPass(activity: Activity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) { + passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { password -> + then(password) + }, { + ToastUtils.warnToast(activity, resourceHelper.gs(canceledMsg)) + }) + } + + private fun askForMasterPassIfNeeded(activity: Activity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) { + if (prefsEncryptionIsDisabled()) { + then("") + } else { + askForMasterPass(activity, canceledMsg, then) + } + } + + private fun assureMasterPasswordSet(activity: Activity, @StringRes wrongPwdTitle: Int): Boolean { + if (!sp.contains(R.string.key_master_password) || (sp.getString(R.string.key_master_password, "") == "")) { + WarningDialog.showWarning(activity, + resourceHelper.gs(wrongPwdTitle), + resourceHelper.gs(R.string.master_password_missing, resourceHelper.gs(R.string.configbuilder_general), resourceHelper.gs(R.string.protection)), + R.string.nav_preferences, { + val intent = Intent(activity, PreferencesActivity::class.java).apply { + putExtra("id", R.xml.pref_general) + } + activity.startActivity(intent) + }) + return false + } + return true + } + + private fun askToConfirmExport(activity: Activity, then: ((password: String) -> Unit)) { + if (!prefsEncryptionIsDisabled() && !assureMasterPasswordSet(activity, R.string.nav_export)) return + + TwoMessagesAlertDialog.showAlert(activity, resourceHelper.gs(R.string.nav_export), + resourceHelper.gs(R.string.export_to) + " " + encFile + " ?", + resourceHelper.gs(R.string.password_preferences_encrypt_prompt), { + askForMasterPassIfNeeded(activity, R.string.preferences_export_canceled, then) + }, null, R.drawable.ic_header_export) + } + + private fun askToConfirmImport(activity: Activity, fileToImport: File, then: ((password: String) -> Unit)) { + + if (encFile.exists()) { + if (!assureMasterPasswordSet(activity, R.string.nav_import)) return + + TwoMessagesAlertDialog.showAlert(activity, resourceHelper.gs(R.string.nav_import), + resourceHelper.gs(R.string.import_from) + " " + fileToImport + " ?", + resourceHelper.gs(R.string.password_preferences_decrypt_prompt), { + askForMasterPass(activity, R.string.preferences_import_canceled, then) + }, null, R.drawable.ic_header_import) + + } else { + OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.nav_import), + resourceHelper.gs(R.string.import_from) + " " + fileToImport + " ?", + Runnable { then("") }) + } + } + + private fun exportSharedPreferences(activity: Activity) { + askToConfirmExport(activity) { password -> + try { + val entries: MutableMap = mutableMapOf() + for ((key, value) in sp.getAll()) { + entries[key] = value.toString() + } + + val prefs = Prefs(entries, prepareMetadata(activity)) + + classicPrefsFormat.savePreferences(file, prefs) + encryptedPrefsFormat.savePreferences(encFile, prefs, password) + + ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported)) + } catch (e: FileNotFoundException) { + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + encFile) + log.error(TAG, "Unhandled exception", e) + } catch (e: IOException) { + ToastUtils.errorToast(activity, e.message) + log.error(TAG, "Unhandled exception", e) + } + } + } + + fun importSharedPreferences(fragment: Fragment) { + fragment.activity?.let { importSharedPreferences(it) } + } + + fun importSharedPreferences(activity: Activity) { + + val importFile = prefsImportFile() + + askToConfirmImport(activity, importFile) { password -> + + val format: PrefsFormat = if (encFile.exists()) encryptedPrefsFormat else classicPrefsFormat + + try { + + val prefs = format.loadPreferences(importFile, password) + prefs.metadata = checkMetadata(prefs.metadata) + + // import is OK when we do not have errors (warnings are allowed) + val importOk = checkIfImportIsOk(prefs) + + // if at end we allow to import preferences + val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0) + + PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, { + if (importPossible) { + sp.clear() + for ((key, value) in prefs.values) { + if (value == "true" || value == "false") { + sp.putBoolean(key, value.toBoolean()) + } else { + sp.putString(key, value) + } + } + + restartAppAfterImport(activity) + } else { + // for impossible imports it should not be called + ToastUtils.errorToast(activity, "Cannot import preferences!") + } + }) + + } catch (e: PrefFileNotFoundError) { + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile) + log.error(TAG, "Unhandled exception", e) + } catch (e: PrefIOError) { + log.error(TAG, "Unhandled exception", e) + ToastUtils.errorToast(activity, e.message) + } + } + } + + // check metadata for known issues, change their status and add info with explanations + private fun checkMetadata(metadata: Map): Map { + val meta = metadata.toMutableMap() + + meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour -> + val flavourOfPrefs = flavour.value + if (flavour.value != BuildConfig.FLAVOR) { + flavour.status = PrefsStatus.WARN + flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, BuildConfig.FLAVOR) + } + } + + meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model -> + if (model.value != getCurrentDeviceModelString()) { + model.status = PrefsStatus.WARN + model.info = resourceHelper.gs(R.string.metadata_warning_different_device) + } + } + + meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt -> + try { + val date1 = DateTime.parse(createdAt.value); + val date2 = DateTime.now() + + val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).getDays() + + if (daysOld > IMPORT_AGE_NOT_YET_OLD_DAYS) { + createdAt.status = PrefsStatus.WARN + createdAt.info = resourceHelper.gs(R.string.metadata_warning_old_export, daysOld.toString()) + } + } catch (e: Exception) { + createdAt.status = PrefsStatus.WARN + createdAt.info = resourceHelper.gs(R.string.metadata_warning_date_format) + } + } + + return meta + } + + private fun checkIfImportIsOk(prefs: Prefs): Boolean { + var importOk = true + + for ((_, value) in prefs.metadata) { + if (value.status == PrefsStatus.ERROR) + importOk = false; + } + return importOk + } + + private fun restartAppAfterImport(context: Context) { + sp.putBoolean(R.string.key_setupwizard_processed, true) + show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable { + log.debug(TAG, "Exiting") + rxBus.send(EventAppExit()) + if (context is Activity) { + context.finish() + } + System.runFinalization() + System.exit(0) + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt index 7de5d303ed..2e42294fc1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt @@ -23,6 +23,7 @@ class MaintenanceFragment : DaggerFragment() { @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var foodPlugin: FoodPlugin + @Inject lateinit var importExportPrefs: ImportExportPrefs override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.maintenance_fragment, container, false) @@ -45,13 +46,13 @@ class MaintenanceFragment : DaggerFragment() { } nav_export.setOnClickListener { // start activity for checking permissions... - ImportExportPrefs.verifyStoragePermissions(this) - ImportExportPrefs.exportSharedPreferences(this) + importExportPrefs.verifyStoragePermissions(this) + importExportPrefs.exportSharedPreferences(this) } nav_import.setOnClickListener { // start activity for checking permissions... - ImportExportPrefs.verifyStoragePermissions(this) - ImportExportPrefs.importSharedPreferences(this) + importExportPrefs.verifyStoragePermissions(this) + importExportPrefs.importSharedPreferences(this) } nav_logsettings.setOnClickListener { startActivity(Intent(activity, LogSettingActivity::class.java)) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt index cd4d333995..4a3e5e83ab 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt @@ -4,6 +4,8 @@ import android.content.Context import android.content.Intent import android.net.Uri import androidx.core.content.FileProvider +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference import dagger.android.HasAndroidInjector import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.Config @@ -16,6 +18,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP +import info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference import java.io.* import java.util.* import java.util.zip.ZipEntry @@ -203,4 +206,13 @@ class MaintenancePlugin @Inject constructor( emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) return emailIntent } + + override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) { + super.preprocessPreferences(preferenceFragment) + val encryptSwitch = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs)) as SwitchPreference? + ?: return + encryptSwitch.isVisible = buildHelper.isEngineeringMode() + encryptSwitch.isEnabled = buildHelper.isEngineeringMode() + } + } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt new file mode 100644 index 0000000000..4689f549a6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt @@ -0,0 +1,60 @@ +package info.nightscout.androidaps.plugins.general.maintenance.formats + +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.storage.Storage +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ClassicPrefsFormat @Inject constructor( + private var resourceHelper: ResourceHelper, + private var storage: Storage +) : PrefsFormat { + + companion object { + val FORMAT_KEY = "aaps_old" + } + + override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) { + try { + val contents = prefs.values.entries.joinToString("\n") { entry -> + "${entry.key}::${entry.value}" + } + storage.putFileContents(file, contents) + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } + } + + override fun loadPreferences(file: File, masterPassword: String?): Prefs { + var lineParts: Array + val entries: MutableMap = mutableMapOf() + val metadata: MutableMap = mutableMapOf() + try { + + val rawLines = storage.getFileContents(file).split("\n") + rawLines.forEach { line -> + lineParts = line.split("::").toTypedArray() + if (lineParts.size == 2) { + entries[lineParts[0]] = lineParts[1] + } + } + + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN, resourceHelper.gs(R.string.metadata_warning_outdated_format)) + + return Prefs(entries, metadata) + + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt new file mode 100644 index 0000000000..ec70c679da --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt @@ -0,0 +1,225 @@ +package info.nightscout.androidaps.plugins.general.maintenance.formats + +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.CryptoUtil +import info.nightscout.androidaps.utils.extensions.hexStringToByteArray +import info.nightscout.androidaps.utils.extensions.toHex +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.storage.Storage +import org.json.JSONException +import org.json.JSONObject +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class EncryptedPrefsFormat @Inject constructor( + private var resourceHelper: ResourceHelper, + private var storage: Storage +) : PrefsFormat { + + companion object { + val FORMAT_KEY_ENC = "aaps_encrypted" + val FORMAT_KEY_NOENC = "aaps_structured" + + private val KEY_CONSCIENCE = "if you remove/change this, please make sure you know the consequences!" + } + + override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) { + + val container = JSONObject() + val content = JSONObject() + val meta = JSONObject() + + val encStatus = prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status ?: PrefsStatus.OK + var encrypted = encStatus == PrefsStatus.OK && masterPassword != null + + try { + for ((key, value) in prefs.values.toSortedMap()) { + content.put(key, value) + } + + for ((metaKey, metaEntry) in prefs.metadata) { + if (metaKey == PrefsMetadataKey.FILE_FORMAT) + continue + if (metaKey == PrefsMetadataKey.ENCRYPTION) + continue + meta.put(metaKey.key, metaEntry.value) + } + + container.put(PrefsMetadataKey.FILE_FORMAT.key, if (encrypted) FORMAT_KEY_ENC else FORMAT_KEY_NOENC) + container.put("metadata", meta) + + val security = JSONObject() + security.put("file_hash", "--to-be-calculated--") + var encodedContent = "" + + if (encrypted) { + val salt = CryptoUtil.mineSalt() + val rawContent = content.toString() + val contentAttempt = CryptoUtil.encrypt(masterPassword!!, salt, rawContent) + if (contentAttempt != null) { + encodedContent = contentAttempt + security.put("algorithm", "v1") + security.put("salt", salt.toHex()) + security.put("content_hash", CryptoUtil.sha256(rawContent)) + } else { + // fallback when encryption does not work + encrypted = false + } + } + + if (!encrypted) { + security.put("algorithm", "none") + } + + container.put("security", security) + container.put("content", if (encrypted) encodedContent else content) + + var fileContents = container.toString(2) + val fileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE) + + fileContents = fileContents.replace(Regex("(\\\"file_hash\\\"\\s*\\:\\s*\\\")(--to-be-calculated--)(\\\")"), "$1" + fileHash + "$3") + + storage.putFileContents(file, fileContents) + + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } + } + + override fun loadPreferences(file: File, masterPassword: String?): Prefs { + + val entries: MutableMap = mutableMapOf() + val metadata: MutableMap = mutableMapOf() + val issues = LinkedList() + try { + + val jsonBody = storage.getFileContents(file) + val fileContents = jsonBody.replace(Regex("(?is)(\\\"file_hash\\\"\\s*\\:\\s*\\\")([^\"]*)(\\\")"), "$1--to-be-calculated--$3") + val calculatedFileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE) + val container = JSONObject(jsonBody) + + if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content") && container.has("metadata")) { + val fileFormat = container.getString(PrefsMetadataKey.FILE_FORMAT.key) + + if ((fileFormat != FORMAT_KEY_ENC) && (fileFormat != FORMAT_KEY_NOENC)) { + throw PrefFormatError("Unsupported file format: " + fileFormat) + } + + val meta = container.getJSONObject("metadata") + val security = container.getJSONObject("security") + + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(fileFormat, PrefsStatus.OK) + for (key in meta.keys()) { + val metaKey = PrefsMetadataKey.fromKey(key) + if (metaKey != null) { + metadata[metaKey] = PrefMetadata(meta.getString(key), PrefsStatus.OK) + } + } + + val encrypted = fileFormat == FORMAT_KEY_ENC + var secure: PrefsStatus = PrefsStatus.OK + var decryptedOk = false + var contentJsonObj: JSONObject? = null + var insecurityReason = resourceHelper.gs(R.string.prefdecrypt_settings_tampered) + + if (security.has("file_hash")) { + if (calculatedFileHash != security.getString("file_hash")) { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_modified)) + } + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_missing_file_hash)) + } + + if (encrypted) { + if (security.has("algorithm") && security.get("algorithm") == "v1") { + if (security.has("salt") && security.has("content_hash")) { + + val salt = security.getString("salt").hexStringToByteArray() + val decrypted = CryptoUtil.decrypt(masterPassword!!, salt, container.getString("content")) + + if (decrypted != null) { + try { + val contentHash = CryptoUtil.sha256(decrypted) + + if (contentHash == security.getString("content_hash")) { + contentJsonObj = JSONObject(decrypted) + decryptedOk = true + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_modified)) + } + + } catch (e: JSONException) { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_parsing)) + } + + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_pass)) + insecurityReason = resourceHelper.gs(R.string.prefdecrypt_wrong_password) + } + + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_format)) + } + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_algorithm)) + } + + } else { + + if (secure == PrefsStatus.OK) { + secure = PrefsStatus.WARN + } + + if (!(security.has("algorithm") && security.get("algorithm") == "none")) { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_algorithm)) + } + + contentJsonObj = container.getJSONObject("content") + decryptedOk = true + } + + if (decryptedOk && contentJsonObj != null) { + for (key in contentJsonObj.keys()) { + entries.put(key, contentJsonObj[key].toString()) + } + } + + val issuesStr: String? = if (issues.size > 0) issues.joinToString("\n") else null + val encryptionDescStr = if (encrypted) { + if (secure == PrefsStatus.OK) resourceHelper.gs(R.string.prefdecrypt_settings_secure) else insecurityReason + } else { + if (secure != PrefsStatus.ERROR) resourceHelper.gs(R.string.prefdecrypt_settings_unencrypted) else resourceHelper.gs(R.string.prefdecrypt_settings_tampered) + } + + metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata(encryptionDescStr, secure, issuesStr) + } else { + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(resourceHelper.gs(R.string.prefdecrypt_wrong_json), PrefsStatus.ERROR) + } + + return Prefs(entries, metadata) + + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } catch (e: JSONException) { + throw PrefFormatError("Mallformed preferences JSON file: " + e) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt new file mode 100644 index 0000000000..a47add0dd7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt @@ -0,0 +1,73 @@ +package info.nightscout.androidaps.plugins.general.maintenance.formats + +import android.content.Context +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import info.nightscout.androidaps.R +import java.io.File + +enum class PrefsMetadataKey(val key: String, @DrawableRes val icon:Int, @StringRes val label:Int) { + + FILE_FORMAT("format", R.drawable.ic_meta_format, R.string.metadata_label_format), + CREATED_AT("created_at", R.drawable.ic_meta_date, R.string.metadata_label_created_at), + AAPS_VERSION("aaps_version", R.drawable.ic_meta_version, R.string.metadata_label_aaps_version), + AAPS_FLAVOUR("aaps_flavour", R.drawable.ic_meta_flavour, R.string.metadata_label_aaps_flavour), + DEVICE_NAME("device_name", R.drawable.ic_meta_name, R.string.metadata_label_device_name), + DEVICE_MODEL("device_model", R.drawable.ic_meta_model, R.string.metadata_label_device_model), + ENCRYPTION("encryption", R.drawable.ic_meta_encryption, R.string.metadata_label_encryption); + + companion object { + private val keyToEnumMap = HashMap() + + init { + for (value in values()) { + keyToEnumMap.put(value.key, value) + } + } + + fun fromKey(key: String): PrefsMetadataKey? { + if (keyToEnumMap.containsKey(key)) { + return keyToEnumMap.get(key) + } else { + return null + } + } + + + } + + fun formatForDisplay(context: Context, value:String): String { + return when (this) { + FILE_FORMAT -> when (value) { + ClassicPrefsFormat.FORMAT_KEY -> context.getString(R.string.metadata_format_old) + EncryptedPrefsFormat.FORMAT_KEY_ENC -> context.getString(R.string.metadata_format_new) + EncryptedPrefsFormat.FORMAT_KEY_NOENC -> context.getString(R.string.metadata_format_debug) + else -> context.getString(R.string.metadata_format_other) + } + CREATED_AT -> value.replace("T", " ").replace("Z", " (UTC)") + else -> value + } + } + +} + +data class PrefMetadata(var value : String, var status : PrefsStatus, var info : String? = null) + +data class Prefs(val values : Map, var metadata : Map) + +interface PrefsFormat { + fun savePreferences(file: File, prefs: Prefs, masterPassword: String? = null) + fun loadPreferences(file: File, masterPassword: String? = null) : Prefs +} + +enum class PrefsStatus(@DrawableRes val icon:Int) { + OK(R.drawable.ic_meta_ok), + WARN(R.drawable.ic_meta_warning), + ERROR(R.drawable.ic_meta_error), + UNKNOWN(R.drawable.ic_meta_error), + DISABLED(R.drawable.ic_meta_error) +} + +class PrefFileNotFoundError(message: String) : Exception(message) +class PrefIOError(message: String) : Exception(message) +class PrefFormatError(message: String) : Exception(message) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index eef9521d09..a3d86716bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -1463,7 +1463,7 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList // add basal data if (pump.getPumpDescription().isTempBasalCapable && sp.getBoolean("showbasals", true)) { - graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2d); + graphData.addBasals(fromTime, now, lowLine / graphData.getMaxY() / 1.2d); } // add target line diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java deleted file mode 100644 index bcb11363c8..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java +++ /dev/null @@ -1,726 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.graphData; - -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; - -import com.jjoe64.graphview.GraphView; -import com.jjoe64.graphview.series.BarGraphSeries; -import com.jjoe64.graphview.series.DataPoint; -import com.jjoe64.graphview.series.LineGraphSeries; -import com.jjoe64.graphview.series.Series; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.db.ExtendedBolus; -import info.nightscout.androidaps.db.ProfileSwitch; -import info.nightscout.androidaps.db.TempTarget; -import info.nightscout.androidaps.interfaces.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.TreatmentsInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.APSResult; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.BasalData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.treatments.Treatment; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.Round; -import info.nightscout.androidaps.utils.resources.ResourceHelper; - -/** - * Created by mike on 18.10.2017. - */ - -public class GraphData { - - @Inject AAPSLogger aapsLogger; - @Inject ProfileFunction profileFunction; - @Inject ResourceHelper resourceHelper; - @Inject ActivePluginProvider activePlugin; - - private GraphView graph; - public double maxY = Double.MIN_VALUE; - private double minY = Double.MAX_VALUE; - private List bgReadingsArray; - private String units; - private List series = new ArrayList<>(); - private TreatmentsInterface treatmentsPlugin; - - - private IobCobCalculatorPlugin iobCobCalculatorPlugin; // Cannot be injected: HistoryBrowser - - public GraphData(HasAndroidInjector injector, GraphView graph, IobCobCalculatorPlugin iobCobCalculatorPlugin) { - injector.androidInjector().inject(this); - units = profileFunction.getUnits(); - this.graph = graph; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - treatmentsPlugin = activePlugin.getActiveTreatments(); - } - - public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, List predictions) { - double maxBgValue = Double.MIN_VALUE; - //bgReadingsArray = MainApp.getDbHelper().getBgreadingsDataFromTime(fromTime, true); - bgReadingsArray = iobCobCalculatorPlugin.getBgReadings(); - List bgListArray = new ArrayList<>(); - - if (bgReadingsArray == null || bgReadingsArray.size() == 0) { - aapsLogger.debug(LTag.OVERVIEW, "No BG data."); - maxY = 10; - minY = 0; - return; - } - - for (BgReading bg : bgReadingsArray) { - if (bg.date < fromTime || bg.date > toTime) continue; - if (bg.value > maxBgValue) maxBgValue = bg.value; - bgListArray.add(bg); - } - if (predictions != null) { - Collections.sort(predictions, (o1, o2) -> Double.compare(o1.getX(), o2.getX())); - for (BgReading prediction : predictions) { - if (prediction.value >= 40) - bgListArray.add(prediction); - } - } - - maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units); - maxBgValue = units.equals(Constants.MGDL) ? Round.roundTo(maxBgValue, 40d) + 80 : Round.roundTo(maxBgValue, 2d) + 4; - if (highLine > maxBgValue) maxBgValue = highLine; - int numOfVertLines = units.equals(Constants.MGDL) ? (int) (maxBgValue / 40 + 1) : (int) (maxBgValue / 2 + 1); - - DataPointWithLabelInterface[] bg = new DataPointWithLabelInterface[bgListArray.size()]; - bg = bgListArray.toArray(bg); - - - maxY = maxBgValue; - minY = 0; - // set manual y bounds to have nice steps - graph.getGridLabelRenderer().setNumVerticalLabels(numOfVertLines); - - addSeries(new PointsWithLabelGraphSeries<>(bg)); - } - - public void addInRangeArea(long fromTime, long toTime, double lowLine, double highLine) { - AreaGraphSeries inRangeAreaSeries; - - DoubleDataPoint[] inRangeAreaDataPoints = new DoubleDataPoint[]{ - new DoubleDataPoint(fromTime, lowLine, highLine), - new DoubleDataPoint(toTime, lowLine, highLine) - }; - inRangeAreaSeries = new AreaGraphSeries<>(inRangeAreaDataPoints); - inRangeAreaSeries.setColor(0); - inRangeAreaSeries.setDrawBackground(true); - inRangeAreaSeries.setBackgroundColor(resourceHelper.gc(R.color.inrangebackground)); - - addSeries(inRangeAreaSeries); - } - - // scale in % of vertical size (like 0.3) - public void addBasals(long fromTime, long toTime, double scale) { - LineGraphSeries basalsLineSeries; - LineGraphSeries absoluteBasalsLineSeries; - LineGraphSeries baseBasalsSeries; - LineGraphSeries tempBasalsSeries; - - double maxBasalValueFound = 0d; - Scale basalScale = new Scale(); - - List baseBasalArray = new ArrayList<>(); - List tempBasalArray = new ArrayList<>(); - List basalLineArray = new ArrayList<>(); - List absoluteBasalLineArray = new ArrayList<>(); - double lastLineBasal = 0; - double lastAbsoluteLineBasal = -1; - double lastBaseBasal = 0; - double lastTempBasal = 0; - for (long time = fromTime; time < toTime; time += 60 * 1000L) { - Profile profile = profileFunction.getProfile(time); - if (profile == null) continue; - BasalData basalData = iobCobCalculatorPlugin.getBasalData(profile, time); - double baseBasalValue = basalData.basal; - double absoluteLineValue = baseBasalValue; - double tempBasalValue = 0; - double basal = 0d; - if (basalData.isTempBasalRunning) { - absoluteLineValue = tempBasalValue = basalData.tempBasalAbsolute; - if (tempBasalValue != lastTempBasal) { - tempBasalArray.add(new ScaledDataPoint(time, lastTempBasal, basalScale)); - tempBasalArray.add(new ScaledDataPoint(time, basal = tempBasalValue, basalScale)); - } - if (lastBaseBasal != 0d) { - baseBasalArray.add(new ScaledDataPoint(time, lastBaseBasal, basalScale)); - baseBasalArray.add(new ScaledDataPoint(time, 0d, basalScale)); - lastBaseBasal = 0d; - } - } else { - if (baseBasalValue != lastBaseBasal) { - baseBasalArray.add(new ScaledDataPoint(time, lastBaseBasal, basalScale)); - baseBasalArray.add(new ScaledDataPoint(time, basal = baseBasalValue, basalScale)); - lastBaseBasal = baseBasalValue; - } - if (lastTempBasal != 0) { - tempBasalArray.add(new ScaledDataPoint(time, lastTempBasal, basalScale)); - tempBasalArray.add(new ScaledDataPoint(time, 0d, basalScale)); - } - } - - if (baseBasalValue != lastLineBasal) { - basalLineArray.add(new ScaledDataPoint(time, lastLineBasal, basalScale)); - basalLineArray.add(new ScaledDataPoint(time, baseBasalValue, basalScale)); - } - if (absoluteLineValue != lastAbsoluteLineBasal) { - absoluteBasalLineArray.add(new ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)); - absoluteBasalLineArray.add(new ScaledDataPoint(time, basal, basalScale)); - } - - lastAbsoluteLineBasal = absoluteLineValue; - lastLineBasal = baseBasalValue; - lastTempBasal = tempBasalValue; - maxBasalValueFound = Math.max(maxBasalValueFound, Math.max(tempBasalValue, baseBasalValue)); - } - - basalLineArray.add(new ScaledDataPoint(toTime, lastLineBasal, basalScale)); - baseBasalArray.add(new ScaledDataPoint(toTime, lastBaseBasal, basalScale)); - tempBasalArray.add(new ScaledDataPoint(toTime, lastTempBasal, basalScale)); - absoluteBasalLineArray.add(new ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)); - - ScaledDataPoint[] baseBasal = new ScaledDataPoint[baseBasalArray.size()]; - baseBasal = baseBasalArray.toArray(baseBasal); - baseBasalsSeries = new LineGraphSeries<>(baseBasal); - baseBasalsSeries.setDrawBackground(true); - baseBasalsSeries.setBackgroundColor(resourceHelper.gc(R.color.basebasal)); - baseBasalsSeries.setThickness(0); - - ScaledDataPoint[] tempBasal = new ScaledDataPoint[tempBasalArray.size()]; - tempBasal = tempBasalArray.toArray(tempBasal); - tempBasalsSeries = new LineGraphSeries<>(tempBasal); - tempBasalsSeries.setDrawBackground(true); - tempBasalsSeries.setBackgroundColor(resourceHelper.gc(R.color.tempbasal)); - tempBasalsSeries.setThickness(0); - - ScaledDataPoint[] basalLine = new ScaledDataPoint[basalLineArray.size()]; - basalLine = basalLineArray.toArray(basalLine); - basalsLineSeries = new LineGraphSeries<>(basalLine); - Paint paint = new Paint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(resourceHelper.getDisplayMetrics().scaledDensity * 2); - paint.setPathEffect(new DashPathEffect(new float[]{2, 4}, 0)); - paint.setColor(resourceHelper.gc(R.color.basal)); - basalsLineSeries.setCustomPaint(paint); - - ScaledDataPoint[] absoluteBasalLine = new ScaledDataPoint[absoluteBasalLineArray.size()]; - absoluteBasalLine = absoluteBasalLineArray.toArray(absoluteBasalLine); - absoluteBasalsLineSeries = new LineGraphSeries<>(absoluteBasalLine); - Paint absolutePaint = new Paint(); - absolutePaint.setStyle(Paint.Style.STROKE); - absolutePaint.setStrokeWidth(resourceHelper.getDisplayMetrics().scaledDensity * 2); - absolutePaint.setColor(resourceHelper.gc(R.color.basal)); - absoluteBasalsLineSeries.setCustomPaint(absolutePaint); - - basalScale.setMultiplier(maxY * scale / maxBasalValueFound); - - addSeries(baseBasalsSeries); - addSeries(tempBasalsSeries); - addSeries(basalsLineSeries); - addSeries(absoluteBasalsLineSeries); - } - - public void addTargetLine(long fromTime, long toTime, Profile profile, LoopPlugin.LastRun lastRun) { - LineGraphSeries targetsSeries; - - Scale targetsScale = new Scale(); - targetsScale.setMultiplier(1); - - List targetsSeriesArray = new ArrayList<>(); - double lastTarget = -1; - - if (lastRun != null && lastRun.constraintsProcessed != null) { - APSResult apsResult = lastRun.constraintsProcessed; - long latestPredictionsTime = apsResult.getLatestPredictionsTime(); - if (latestPredictionsTime > toTime) { - toTime = latestPredictionsTime; - } - } - - for (long time = fromTime; time < toTime; time += 5 * 60 * 1000L) { - TempTarget tt = treatmentsPlugin.getTempTargetFromHistory(time); - double value; - if (tt == null) { - value = Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, profileFunction.getUnits()); - } else { - value = Profile.fromMgdlToUnits(tt.target(), profileFunction.getUnits()); - } - if (lastTarget != value) { - if (lastTarget != -1) - targetsSeriesArray.add(new DataPoint(time, lastTarget)); - targetsSeriesArray.add(new DataPoint(time, value)); - } - lastTarget = value; - } - targetsSeriesArray.add(new DataPoint(toTime, lastTarget)); - - DataPoint[] targets = new DataPoint[targetsSeriesArray.size()]; - targets = targetsSeriesArray.toArray(targets); - targetsSeries = new LineGraphSeries<>(targets); - targetsSeries.setDrawBackground(false); - targetsSeries.setColor(resourceHelper.gc(R.color.tempTargetBackground)); - targetsSeries.setThickness(2); - - addSeries(targetsSeries); - } - - public void addTreatments(long fromTime, long endTime) { - List filteredTreatments = new ArrayList<>(); - - List treatments = treatmentsPlugin.getTreatmentsFromHistory(); - - for (int tx = 0; tx < treatments.size(); tx++) { - Treatment t = treatments.get(tx); - if (t.getX() < fromTime || t.getX() > endTime) continue; - if (t.isSMB && !t.isValid) continue; - t.setY(getNearestBg((long) t.getX())); - filteredTreatments.add(t); - } - - // ProfileSwitch - List profileSwitches = treatmentsPlugin.getProfileSwitchesFromHistory().getList(); - - for (int tx = 0; tx < profileSwitches.size(); tx++) { - DataPointWithLabelInterface t = profileSwitches.get(tx); - if (t.getX() < fromTime || t.getX() > endTime) continue; - filteredTreatments.add(t); - } - - // Extended bolus - if (!activePlugin.getActivePump().isFakingTempsByExtendedBoluses()) { - List extendedBoluses = treatmentsPlugin.getExtendedBolusesFromHistory().getList(); - - for (int tx = 0; tx < extendedBoluses.size(); tx++) { - DataPointWithLabelInterface t = extendedBoluses.get(tx); - if (t.getX() + t.getDuration() < fromTime || t.getX() > endTime) continue; - if (t.getDuration() == 0) continue; - t.setY(getNearestBg((long) t.getX())); - filteredTreatments.add(t); - } - } - - // Careportal - List careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true); - - for (int tx = 0; tx < careportalEvents.size(); tx++) { - DataPointWithLabelInterface t = careportalEvents.get(tx); - if (t.getX() + t.getDuration() < fromTime || t.getX() > endTime) continue; - t.setY(getNearestBg((long) t.getX())); - filteredTreatments.add(t); - } - - DataPointWithLabelInterface[] treatmentsArray = new DataPointWithLabelInterface[filteredTreatments.size()]; - treatmentsArray = filteredTreatments.toArray(treatmentsArray); - addSeries(new PointsWithLabelGraphSeries<>(treatmentsArray)); - } - - private double getNearestBg(long date) { - if (bgReadingsArray == null) - return Profile.fromMgdlToUnits(100, units); - for (int r = 0; r < bgReadingsArray.size(); r++) { - BgReading reading = bgReadingsArray.get(r); - if (reading.date > date) continue; - return Profile.fromMgdlToUnits(reading.value, units); - } - return bgReadingsArray.size() > 0 - ? Profile.fromMgdlToUnits(bgReadingsArray.get(0).value, units) : Profile.fromMgdlToUnits(100, units); - } - - public void addActivity(long fromTime, long toTime, boolean useForScale, double scale) { - FixedLineGraphSeries actSeriesHist; - List actArrayHist = new ArrayList<>(); - FixedLineGraphSeries actSeriesPred; - List actArrayPred = new ArrayList<>(); - - double now = System.currentTimeMillis(); - Scale actScale = new Scale(); - IobTotal total; - double maxIAValue = 0; - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - Profile profile = profileFunction.getProfile(time); - double act; - if (profile == null) continue; - total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile); - act = total.activity; - - if (time <= now) - actArrayHist.add(new ScaledDataPoint(time, act, actScale)); - else - actArrayPred.add(new ScaledDataPoint(time, act, actScale)); - - maxIAValue = Math.max(maxIAValue, Math.abs(act)); - } - - ScaledDataPoint[] actData = new ScaledDataPoint[actArrayHist.size()]; - actData = actArrayHist.toArray(actData); - actSeriesHist = new FixedLineGraphSeries<>(actData); - actSeriesHist.setDrawBackground(false); - actSeriesHist.setColor(resourceHelper.gc(R.color.activity)); - actSeriesHist.setThickness(3); - - addSeries(actSeriesHist); - - actData = new ScaledDataPoint[actArrayPred.size()]; - actData = actArrayPred.toArray(actData); - actSeriesPred = new FixedLineGraphSeries<>(actData); - - Paint paint = new Paint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(3); - paint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0)); - paint.setColor(resourceHelper.gc(R.color.activity)); - actSeriesPred.setCustomPaint(paint); - - if (useForScale) { - maxY = maxIAValue; - minY = -maxIAValue; - } - actScale.setMultiplier(maxY * scale / maxIAValue); - - addSeries(actSeriesPred); - } - - // scale in % of vertical size (like 0.3) - public void addIob(long fromTime, long toTime, boolean useForScale, double scale, boolean showPrediction) { - FixedLineGraphSeries iobSeries; - List iobArray = new ArrayList<>(); - Double maxIobValueFound = Double.MIN_VALUE; - double lastIob = 0; - Scale iobScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - Profile profile = profileFunction.getProfile(time); - double iob = 0d; - if (profile != null) - iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob; - if (Math.abs(lastIob - iob) > 0.02) { - if (Math.abs(lastIob - iob) > 0.2) - iobArray.add(new ScaledDataPoint(time, lastIob, iobScale)); - iobArray.add(new ScaledDataPoint(time, iob, iobScale)); - maxIobValueFound = Math.max(maxIobValueFound, Math.abs(iob)); - lastIob = iob; - } - } - - ScaledDataPoint[] iobData = new ScaledDataPoint[iobArray.size()]; - iobData = iobArray.toArray(iobData); - iobSeries = new FixedLineGraphSeries<>(iobData); - iobSeries.setDrawBackground(true); - iobSeries.setBackgroundColor(0x80FFFFFF & resourceHelper.gc(R.color.iob)); //50% - iobSeries.setColor(resourceHelper.gc(R.color.iob)); - iobSeries.setThickness(3); - - if (showPrediction) { - AutosensResult lastAutosensResult; - AutosensData autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData"); - if (autosensData == null) - lastAutosensResult = new AutosensResult(); - else - lastAutosensResult = autosensData.autosensResult; - boolean isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null; - - List iobPred = new ArrayList<>(); - IobTotal[] iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget); - for (IobTotal i : iobPredArray) { - iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))); - maxIobValueFound = Math.max(maxIobValueFound, Math.abs(i.iob)); - } - DataPointWithLabelInterface[] iobp = new DataPointWithLabelInterface[iobPred.size()]; - iobp = iobPred.toArray(iobp); - addSeries(new PointsWithLabelGraphSeries<>(iobp)); - - - List iobPred2 = new ArrayList<>(); - IobTotal[] iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(new AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget); - for (IobTotal i : iobPredArray2) { - iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred))); - maxIobValueFound = Math.max(maxIobValueFound, Math.abs(i.iob)); - } - DataPointWithLabelInterface[] iobp2 = new DataPointWithLabelInterface[iobPred2.size()]; - iobp2 = iobPred2.toArray(iobp2); - addSeries(new PointsWithLabelGraphSeries<>(iobp2)); - - aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray)); - aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2)); - } - - if (useForScale) { - maxY = maxIobValueFound; - minY = -maxIobValueFound; - } - - iobScale.setMultiplier(maxY * scale / maxIobValueFound); - - addSeries(iobSeries); - } - - // scale in % of vertical size (like 0.3) - public void addCob(long fromTime, long toTime, boolean useForScale, double scale) { - List minFailoverActiveList = new ArrayList<>(); - FixedLineGraphSeries cobSeries; - List cobArray = new ArrayList<>(); - Double maxCobValueFound = 0d; - int lastCob = 0; - Scale cobScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - int cob = (int) autosensData.cob; - if (cob != lastCob) { - if (autosensData.carbsFromBolus > 0) - cobArray.add(new ScaledDataPoint(time, lastCob, cobScale)); - cobArray.add(new ScaledDataPoint(time, cob, cobScale)); - maxCobValueFound = Math.max(maxCobValueFound, cob); - lastCob = cob; - } - if (autosensData.failoverToMinAbsorbtionRate) { - autosensData.setScale(cobScale); - autosensData.setChartTime(time); - minFailoverActiveList.add(autosensData); - } - } - } - - // COB - ScaledDataPoint[] cobData = new ScaledDataPoint[cobArray.size()]; - cobData = cobArray.toArray(cobData); - cobSeries = new FixedLineGraphSeries<>(cobData); - cobSeries.setDrawBackground(true); - cobSeries.setBackgroundColor(0x80FFFFFF & resourceHelper.gc(R.color.cob)); //50% - cobSeries.setColor(resourceHelper.gc(R.color.cob)); - cobSeries.setThickness(3); - - if (useForScale) { - maxY = maxCobValueFound; - minY = 0; - } - - cobScale.setMultiplier(maxY * scale / maxCobValueFound); - - addSeries(cobSeries); - - DataPointWithLabelInterface[] minFailover = new DataPointWithLabelInterface[minFailoverActiveList.size()]; - minFailover = minFailoverActiveList.toArray(minFailover); - addSeries(new PointsWithLabelGraphSeries<>(minFailover)); - } - - // scale in % of vertical size (like 0.3) - public void addDeviations(long fromTime, long toTime, boolean useForScale, double scale) { - class DeviationDataPoint extends ScaledDataPoint { - public int color; - - private DeviationDataPoint(double x, double y, int color, Scale scale) { - super(x, y, scale); - this.color = color; - } - } - - BarGraphSeries devSeries; - List devArray = new ArrayList<>(); - Double maxDevValueFound = 0d; - Scale devScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - int color = resourceHelper.gc(R.color.deviationblack); // "=" - if (autosensData.type.equals("") || autosensData.type.equals("non-meal")) { - if (autosensData.pastSensitivity.equals("C")) - color = resourceHelper.gc(R.color.deviationgrey); - if (autosensData.pastSensitivity.equals("+")) - color = resourceHelper.gc(R.color.deviationgreen); - if (autosensData.pastSensitivity.equals("-")) - color = resourceHelper.gc(R.color.deviationred); - } else if (autosensData.type.equals("uam")) { - color = resourceHelper.gc(R.color.uam); - } else if (autosensData.type.equals("csf")) { - color = resourceHelper.gc(R.color.deviationgrey); - } - devArray.add(new DeviationDataPoint(time, autosensData.deviation, color, devScale)); - maxDevValueFound = Math.max(maxDevValueFound, Math.abs(autosensData.deviation)); - } - } - - // DEVIATIONS - DeviationDataPoint[] devData = new DeviationDataPoint[devArray.size()]; - devData = devArray.toArray(devData); - devSeries = new BarGraphSeries<>(devData); - devSeries.setValueDependentColor(data -> data.color); - - if (useForScale) { - maxY = maxDevValueFound; - minY = -maxY; - } - - devScale.setMultiplier(maxY * scale / maxDevValueFound); - - addSeries(devSeries); - } - - // scale in % of vertical size (like 0.3) - public void addRatio(long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries ratioSeries; - List ratioArray = new ArrayList<>(); - double maxRatioValueFound = Double.MIN_VALUE; - double minRatioValueFound = Double.MAX_VALUE; - Scale ratioScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - ratioArray.add(new ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale)); - maxRatioValueFound = Math.max(maxRatioValueFound, autosensData.autosensResult.ratio - 1); - minRatioValueFound = Math.min(minRatioValueFound, autosensData.autosensResult.ratio - 1); - } - } - - // RATIOS - ScaledDataPoint[] ratioData = new ScaledDataPoint[ratioArray.size()]; - ratioData = ratioArray.toArray(ratioData); - ratioSeries = new LineGraphSeries<>(ratioData); - ratioSeries.setColor(resourceHelper.gc(R.color.ratio)); - ratioSeries.setThickness(3); - - if (useForScale) { - maxY = Math.max(maxRatioValueFound, Math.abs(minRatioValueFound)); - minY = -maxY; - } - - ratioScale.setMultiplier(maxY * scale / Math.max(maxRatioValueFound, Math.abs(minRatioValueFound))); - - addSeries(ratioSeries); - } - - // scale in % of vertical size (like 0.3) - public void addDeviationSlope(long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries dsMaxSeries; - LineGraphSeries dsMinSeries; - List dsMaxArray = new ArrayList<>(); - List dsMinArray = new ArrayList<>(); - double maxFromMaxValueFound = 0d; - double maxFromMinValueFound = 0d; - Scale dsMaxScale = new Scale(); - Scale dsMinScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - dsMaxArray.add(new ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)); - dsMinArray.add(new ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)); - maxFromMaxValueFound = Math.max(maxFromMaxValueFound, Math.abs(autosensData.slopeFromMaxDeviation)); - maxFromMinValueFound = Math.max(maxFromMinValueFound, Math.abs(autosensData.slopeFromMinDeviation)); - } - } - - // Slopes - ScaledDataPoint[] ratioMaxData = new ScaledDataPoint[dsMaxArray.size()]; - ratioMaxData = dsMaxArray.toArray(ratioMaxData); - dsMaxSeries = new LineGraphSeries<>(ratioMaxData); - dsMaxSeries.setColor(resourceHelper.gc(R.color.devslopepos)); - dsMaxSeries.setThickness(3); - - ScaledDataPoint[] ratioMinData = new ScaledDataPoint[dsMinArray.size()]; - ratioMinData = dsMinArray.toArray(ratioMinData); - dsMinSeries = new LineGraphSeries<>(ratioMinData); - dsMinSeries.setColor(resourceHelper.gc(R.color.devslopeneg)); - dsMinSeries.setThickness(3); - - if (useForScale) { - maxY = Math.max(maxFromMaxValueFound, maxFromMinValueFound); - minY = -maxY; - } - - dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound); - dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound); - - addSeries(dsMaxSeries); - addSeries(dsMinSeries); - } - - // scale in % of vertical size (like 0.3) - public void addNowLine(long now) { - LineGraphSeries seriesNow; - DataPoint[] nowPoints = new DataPoint[]{ - new DataPoint(now, 0), - new DataPoint(now, maxY) - }; - - seriesNow = new LineGraphSeries<>(nowPoints); - seriesNow.setDrawDataPoints(false); - // custom paint to make a dotted line - Paint paint = new Paint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(2); - paint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); - paint.setColor(Color.WHITE); - seriesNow.setCustomPaint(paint); - - addSeries(seriesNow); - } - - public void formatAxis(long fromTime, long endTime) { - graph.getViewport().setMaxX(endTime); - graph.getViewport().setMinX(fromTime); - graph.getViewport().setXAxisBoundsManual(true); - graph.getGridLabelRenderer().setLabelFormatter(new TimeAsXAxisLabelFormatter("HH")); - graph.getGridLabelRenderer().setNumHorizontalLabels(7); // only 7 because of the space - } - - private void addSeries(Series s) { - series.add(s); - } - - public void performUpdate() { - // clear old data - graph.getSeries().clear(); - - // add precalculated series - for (Series s : series) { - if (!s.isEmpty()) { - s.onGraphViewAttached(graph); - graph.getSeries().add(s); - } - } - - double step = 1d; - if (maxY < 1) step = 0.1d; - graph.getViewport().setMaxY(Round.ceilTo(maxY, step)); - graph.getViewport().setMinY(Round.floorTo(minY, step)); - graph.getViewport().setYAxisBoundsManual(true); - - // draw it - graph.onDataChanged(false, false); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt new file mode 100644 index 0000000000..fc7d1eb2a1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt @@ -0,0 +1,569 @@ +package info.nightscout.androidaps.plugins.general.overview.graphData + +import android.graphics.Color +import android.graphics.DashPathEffect +import android.graphics.Paint +import com.jjoe64.graphview.GraphView +import com.jjoe64.graphview.series.BarGraphSeries +import com.jjoe64.graphview.series.DataPoint +import com.jjoe64.graphview.series.LineGraphSeries +import com.jjoe64.graphview.series.Series +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.db.BgReading +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.interfaces.TreatmentsInterface +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin.LastRun +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.Round +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.* +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +class GraphData(injector: HasAndroidInjector, private val graph: GraphView, private val iobCobCalculatorPlugin: IobCobCalculatorPlugin) { + + // IobCobCalculatorPlugin Cannot be injected: HistoryBrowser + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var activePlugin: ActivePluginProvider + + private val treatmentsPlugin: TreatmentsInterface + + var maxY = Double.MIN_VALUE + private var minY = Double.MAX_VALUE + private var bgReadingsArray: List? = null + private val units: String + private val series: MutableList> = ArrayList() + + init { + injector.androidInjector().inject(this) + units = profileFunction.getUnits() + treatmentsPlugin = activePlugin.activeTreatments + } + + @Suppress("UNUSED_PARAMETER") + fun addBgReadings(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double, predictions: MutableList?) { + var maxBgValue = Double.MIN_VALUE + bgReadingsArray = iobCobCalculatorPlugin.bgReadings + if (bgReadingsArray?.isEmpty() != false) { + aapsLogger.debug(LTag.OVERVIEW, "No BG data.") + maxY = 10.0 + minY = 0.0 + return + } + val bgListArray: MutableList = ArrayList() + for (bg in bgReadingsArray!!) { + if (bg.date < fromTime || bg.date > toTime) continue + if (bg.value > maxBgValue) maxBgValue = bg.value + bgListArray.add(bg) + } + if (predictions != null) { + predictions.sortWith(Comparator { o1: BgReading, o2: BgReading -> o1.x.compareTo(o2.x) }) + for (prediction in predictions) if (prediction.value >= 40) bgListArray.add(prediction) + } + maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units) + maxBgValue = if (units == Constants.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 + if (highLine > maxBgValue) maxBgValue = highLine + val numOfVerticalLines = if (units == Constants.MGDL) (maxBgValue / 40 + 1).toInt() else (maxBgValue / 2 + 1).toInt() + maxY = maxBgValue + minY = 0.0 + // set manual y bounds to have nice steps + graph.gridLabelRenderer.numVerticalLabels = numOfVerticalLines + addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })) + } + + fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) { + val inRangeAreaSeries: AreaGraphSeries + val inRangeAreaDataPoints = arrayOf( + DoubleDataPoint(fromTime.toDouble(), lowLine, highLine), + DoubleDataPoint(toTime.toDouble(), lowLine, highLine) + ) + inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints) + inRangeAreaSeries.color = 0 + inRangeAreaSeries.isDrawBackground = true + inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground) + addSeries(inRangeAreaSeries) + } + + // scale in % of vertical size (like 0.3) + fun addBasals(fromTime: Long, toTime: Long, scale: Double) { + var maxBasalValueFound = 0.0 + val basalScale = Scale() + val baseBasalArray: MutableList = ArrayList() + val tempBasalArray: MutableList = ArrayList() + val basalLineArray: MutableList = ArrayList() + val absoluteBasalLineArray: MutableList = ArrayList() + var lastLineBasal = 0.0 + var lastAbsoluteLineBasal = -1.0 + var lastBaseBasal = 0.0 + var lastTempBasal = 0.0 + var time = fromTime + while (time < toTime) { + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 60 * 1000L + continue + } + val basalData = iobCobCalculatorPlugin.getBasalData(profile, time) + val baseBasalValue = basalData.basal + var absoluteLineValue = baseBasalValue + var tempBasalValue = 0.0 + var basal = 0.0 + if (basalData.isTempBasalRunning) { + tempBasalValue = basalData.tempBasalAbsolute + absoluteLineValue = tempBasalValue + if (tempBasalValue != lastTempBasal) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale)) + } + if (lastBaseBasal != 0.0) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) + lastBaseBasal = 0.0 + } + } else { + if (baseBasalValue != lastBaseBasal) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale)) + lastBaseBasal = baseBasalValue + } + if (lastTempBasal != 0.0) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) + } + } + if (baseBasalValue != lastLineBasal) { + basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale)) + basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale)) + } + if (absoluteLineValue != lastAbsoluteLineBasal) { + absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale)) + } + lastAbsoluteLineBasal = absoluteLineValue + lastLineBasal = baseBasalValue + lastTempBasal = tempBasalValue + maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue)) + time += 60 * 1000L + } + + // final points + basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)) + + // create series + addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.basebasal) + it.thickness = 0 + }) + addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.tempbasal) + it.thickness = 0 + }) + addSeries(LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.basal) + }) + }) + addSeries(LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { + it.setCustomPaint(Paint().also { absolutePaint -> + absolutePaint.style = Paint.Style.STROKE + absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + absolutePaint.color = resourceHelper.gc(R.color.basal) + }) + }) + basalScale.setMultiplier(maxY * scale / maxBasalValueFound) + } + + fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LastRun?) { + var toTime = toTimeParam + val targetsSeriesArray: MutableList = ArrayList() + var lastTarget = -1.0 + lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } + var time = fromTime + while (time < toTime) { + val tt = treatmentsPlugin.getTempTargetFromHistory(time) + var value: Double + value = if (tt == null) { + Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) + } else { + Profile.fromMgdlToUnits(tt.target(), units) + } + if (lastTarget != value) { + if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) + targetsSeriesArray.add(DataPoint(time.toDouble(), value)) + } + lastTarget = value + time += 5 * 60 * 1000L + } + // final point + targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) + // create series + addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.tempTargetBackground) + it.thickness = 2 + }) + } + + fun addTreatments(fromTime: Long, endTime: Long) { + val filteredTreatments: MutableList = ArrayList() + val treatments = treatmentsPlugin.treatmentsFromHistory + for (tx in treatments.indices) { + val t = treatments[tx] + if (t.x < fromTime || t.x > endTime) continue + if (t.isSMB && !t.isValid) continue + t.y = getNearestBg(t.x.toLong()) + filteredTreatments.add(t) + } + + // ProfileSwitch + val profileSwitches = treatmentsPlugin.profileSwitchesFromHistory.list + for (tx in profileSwitches.indices) { + val t: DataPointWithLabelInterface = profileSwitches[tx] + if (t.x < fromTime || t.x > endTime) continue + filteredTreatments.add(t) + } + + // Extended bolus + if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { + val extendedBoluses = treatmentsPlugin.extendedBolusesFromHistory.list + for (tx in extendedBoluses.indices) { + val t: DataPointWithLabelInterface = extendedBoluses[tx] + if (t.x + t.duration < fromTime || t.x > endTime) continue + if (t.duration == 0L) continue + t.y = getNearestBg(t.x.toLong()) + filteredTreatments.add(t) + } + } + + // Careportal + val careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true) + for (tx in careportalEvents.indices) { + val t: DataPointWithLabelInterface = careportalEvents[tx] + if (t.x + t.duration < fromTime || t.x > endTime) continue + t.y = getNearestBg(t.x.toLong()) + filteredTreatments.add(t) + } + addSeries(PointsWithLabelGraphSeries(Array(filteredTreatments.size) { i -> filteredTreatments[i] })) + } + + private fun getNearestBg(date: Long): Double { + bgReadingsArray?.let { bgReadingsArray -> + for (r in bgReadingsArray.indices) { + val reading = bgReadingsArray[r] + if (reading.date > date) continue + return Profile.fromMgdlToUnits(reading.value, units) + } + return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units) + } ?: return Profile.fromMgdlToUnits(100.0, units) + } + + fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val actArrayHist: MutableList = ArrayList() + val actArrayPred: MutableList = ArrayList() + val now = System.currentTimeMillis().toDouble() + val actScale = Scale() + var total: IobTotal + var maxIAValue = 0.0 + var time = fromTime + while (time <= toTime) { + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 5 * 60 * 1000L + continue + } + total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile) + val act: Double = total.activity + if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPred.add(ScaledDataPoint(time, act, actScale)) + maxIAValue = max(maxIAValue, abs(act)) + time += 5 * 60 * 1000L + } + addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.activity) + it.thickness = 3 + }) + addSeries(FixedLineGraphSeries(Array(actArrayPred.size) { i -> actArrayPred[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 3f + paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.activity) + }) + }) + if (useForScale) { + maxY = maxIAValue + minY = -maxIAValue + } + actScale.setMultiplier(maxY * scale / maxIAValue) + } + + // scale in % of vertical size (like 0.3) + fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean) { + val iobSeries: FixedLineGraphSeries + val iobArray: MutableList = ArrayList() + var maxIobValueFound = Double.MIN_VALUE + var lastIob = 0.0 + val iobScale = Scale() + var time = fromTime + while (time <= toTime) { + val profile = profileFunction.getProfile(time) + var iob = 0.0 + if (profile != null) iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob + if (abs(lastIob - iob) > 0.02) { + if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) + iobArray.add(ScaledDataPoint(time, iob, iobScale)) + maxIobValueFound = max(maxIobValueFound, abs(iob)) + lastIob = iob + } + time += 5 * 60 * 1000L + } + iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + if (showPrediction) { + val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData") + val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() + val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null + val iobPred: MutableList = ArrayList() + val iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredArray) { + iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + addSeries(PointsWithLabelGraphSeries(Array(iobPred.size) { i -> iobPred[i] })) + val iobPred2: MutableList = ArrayList() + val iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredArray2) { + iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + addSeries(PointsWithLabelGraphSeries(Array(iobPred2.size) { i -> iobPred2[i] })) + aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray)) + aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2)) + } + if (useForScale) { + maxY = maxIobValueFound + minY = -maxIobValueFound + } + iobScale.setMultiplier(maxY * scale / maxIobValueFound) + addSeries(iobSeries) + } + + // scale in % of vertical size (like 0.3) + fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val minFailOverActiveList: MutableList = ArrayList() + val cobArray: MutableList = ArrayList() + var maxCobValueFound = 0.0 + var lastCob = 0 + val cobScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + val cob = autosensData.cob.toInt() + if (cob != lastCob) { + if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale)) + cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale)) + maxCobValueFound = max(maxCobValueFound, cob.toDouble()) + lastCob = cob + } + if (autosensData.failoverToMinAbsorbtionRate) { + autosensData.setScale(cobScale) + autosensData.setChartTime(time) + minFailOverActiveList.add(autosensData) + } + } + time += 5 * 60 * 1000L + } + + // COB + addSeries(FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% + it.color = resourceHelper.gc(R.color.cob) + it.thickness = 3 + }) + if (useForScale) { + maxY = maxCobValueFound + minY = 0.0 + } + cobScale.setMultiplier(maxY * scale / maxCobValueFound) + addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })) + } + + // scale in % of vertical size (like 0.3) + fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale) + + val devArray: MutableList = ArrayList() + var maxDevValueFound = 0.0 + val devScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + var color = resourceHelper.gc(R.color.deviationblack) // "=" + if (autosensData.type == "" || autosensData.type == "non-meal") { + if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) + if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) + if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) + } else if (autosensData.type == "uam") { + color = resourceHelper.gc(R.color.uam) + } else if (autosensData.type == "csf") { + color = resourceHelper.gc(R.color.deviationgrey) + } + devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale)) + maxDevValueFound = max(maxDevValueFound, abs(autosensData.deviation)) + } + time += 5 * 60 * 1000L + } + + // DEVIATIONS + addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { + it.setValueDependentColor { data: DeviationDataPoint -> data.color } + }) + if (useForScale) { + maxY = maxDevValueFound + minY = -maxY + } + devScale.setMultiplier(maxY * scale / maxDevValueFound) + } + + // scale in % of vertical size (like 0.3) + fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val ratioArray: MutableList = ArrayList() + var maxRatioValueFound = Double.MIN_VALUE + var minRatioValueFound = Double.MAX_VALUE + val ratioScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + ratioArray.add(ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale)) + maxRatioValueFound = max(maxRatioValueFound, autosensData.autosensResult.ratio - 1) + minRatioValueFound = min(minRatioValueFound, autosensData.autosensResult.ratio - 1) + } + time += 5 * 60 * 1000L + } + + // RATIOS + addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { + it.color = resourceHelper.gc(R.color.ratio) + it.thickness = 3 + }) + if (useForScale) { + maxY = max(maxRatioValueFound, abs(minRatioValueFound)) + minY = -maxY + } + ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound))) + } + + // scale in % of vertical size (like 0.3) + fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val dsMaxArray: MutableList = ArrayList() + val dsMinArray: MutableList = ArrayList() + var maxFromMaxValueFound = 0.0 + var maxFromMinValueFound = 0.0 + val dsMaxScale = Scale() + val dsMinScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)) + dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)) + maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) + maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) + } + time += 5 * 60 * 1000L + } + + // Slopes + addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopepos) + it.thickness = 3 + }) + addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopeneg) + it.thickness = 3 + }) + if (useForScale) { + maxY = max(maxFromMaxValueFound, maxFromMinValueFound) + minY = -maxY + } + dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound) + dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound) + } + + // scale in % of vertical size (like 0.3) + fun addNowLine(now: Long) { + val nowPoints = arrayOf( + DataPoint(now.toDouble(), 0.0), + DataPoint(now.toDouble(), maxY) + ) + addSeries(LineGraphSeries(nowPoints).also { + it.isDrawDataPoints = false + // custom paint to make a dotted line + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 2f + paint.pathEffect = DashPathEffect(floatArrayOf(10f, 20f), 0f) + paint.color = Color.WHITE + }) + }) + } + + fun formatAxis(fromTime: Long, endTime: Long) { + graph.viewport.setMaxX(endTime.toDouble()) + graph.viewport.setMinX(fromTime.toDouble()) + graph.viewport.isXAxisBoundsManual = true + graph.gridLabelRenderer.labelFormatter = TimeAsXAxisLabelFormatter("HH") + graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space + } + + private fun addSeries(s: Series<*>) = series.add(s) + + fun performUpdate() { + // clear old data + graph.series.clear() + + // add pre calculated series + for (s in series) { + if (!s.isEmpty) { + s.onGraphViewAttached(graph) + graph.series.add(s) + } + } + var step = 1.0 + if (maxY < 1) step = 0.1 + graph.viewport.setMaxY(Round.ceilTo(maxY, step)) + graph.viewport.setMinY(Round.floorTo(minY, step)) + graph.viewport.isYAxisBoundsManual = true + + // draw it + graph.onDataChanged(false, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java index ac63790a71..8a00446092 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java @@ -23,6 +23,12 @@ public class ScaledDataPoint implements DataPointInterface, Serializable { this.scale = scale; } + public ScaledDataPoint(long x, double y, Scale scale) { + this.x=x; + this.y=y; + this.scale = scale; + } + public ScaledDataPoint(Date x, double y, Scale scale) { this.x = x.getTime(); this.y = y; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java index 85effcb113..3089ca1a7c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java @@ -64,11 +64,11 @@ public class Notification { public static final int DST_IN_24H = 50; public static final int DISKFULL = 51; public static final int OLDVERSION = 52; - public static final int USERMESSAGE = 53; public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54; public static final int INVALID_VERSION = 55; public static final int PERMISSION_SYSTEM_WINDOW = 56; + public static final int USERMESSAGE = 1000; public int id; public long date; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt new file mode 100644 index 0000000000..3d198dfc0b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt @@ -0,0 +1,12 @@ +package info.nightscout.androidaps.plugins.general.overview.notifications + +class NotificationUserMessage (text :String): Notification() { + + init { + var hash = text.hashCode() + if (hash < USERMESSAGE) hash += USERMESSAGE + id = hash + this.text = text + level = URGENT + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java index a4f66ffb8a..743b0109f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java @@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.danaR.services; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.IBinder; @@ -20,6 +19,9 @@ import dagger.android.HasAndroidInjector; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.events.EventBTChange; +import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; @@ -41,9 +43,12 @@ import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgPCCommStop; import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.ToastUtils; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 28.01.2018. @@ -57,6 +62,9 @@ public abstract class AbstractDanaRExecutionService extends DaggerService { @Inject Context context; @Inject ResourceHelper resourceHelper; @Inject DanaRPump danaRPump; + @Inject FabricPrivacy fabricPrivacy; + + private CompositeDisposable disposable = new CompositeDisposable(); protected String mDevName; @@ -101,22 +109,41 @@ public abstract class AbstractDanaRExecutionService extends DaggerService { public abstract PumpEnactResult setUserOptions(); - protected BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - String action = intent.getAction(); - if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { - aapsLogger.debug(LTag.PUMP, "Device was disconnected " + device.getName());//Device was disconnected - if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) { - if (mSerialIOThread != null) { - mSerialIOThread.disconnect("BT disconnection broadcast"); + @Override public void onCreate() { + super.onCreate(); + disposable.add(rxBus + .toObservable(EventBTChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (event.getState() == EventBTChange.Change.DISCONNECT) { + aapsLogger.debug(LTag.PUMP, "Device was disconnected " + event.getDeviceName());//Device was disconnected + if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(event.getDeviceName())) { + if (mSerialIOThread != null) { + mSerialIOThread.disconnect("BT disconnection broadcast"); + } + rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)); + } } - rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)); - } - } - } - }; + }, fabricPrivacy::logException) + ); + disposable.add(rxBus + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + aapsLogger.debug(LTag.PUMP, "EventAppExit received"); + if (mSerialIOThread != null) + mSerialIOThread.disconnect("Application exit"); + stopSelf(); + }, fabricPrivacy::logException) + ); + + } + + @Override + public void onDestroy() { + disposable.clear(); + super.onDestroy(); + } @Override public IBinder onBind(Intent intent) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java index 4178bd3d57..19f0149e6f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java @@ -92,42 +92,13 @@ public class DanaRExecutionService extends AbstractDanaRExecutionService { @Inject ProfileFunction profileFunction; @Inject SP sp; - private CompositeDisposable disposable = new CompositeDisposable(); - public DanaRExecutionService() { - } @Override public void onCreate() { super.onCreate(); mBinder = new LocalBinder(); - context.registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - }, exception -> FabricPrivacy.getInstance().logException(exception)) - ); - disposable.add(rxBus - .toObservable(EventAppExit.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - aapsLogger.debug(LTag.PUMP, "EventAppExit received"); - if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - context.unregisterReceiver(receiver); - stopSelf(); - }, exception -> FabricPrivacy.getInstance().logException(exception)) - ); - } - - @Override - public void onDestroy() { - disposable.clear(); - super.onDestroy(); } public class LocalBinder extends Binder { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java index d40583e9db..8bcba9ce26 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java @@ -83,8 +83,6 @@ public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService { @Inject ActivePluginProvider activePlugin; @Inject ProfileFunction profileFunction; - private CompositeDisposable disposable = new CompositeDisposable(); - public DanaRKoreanExecutionService() { } @@ -92,33 +90,6 @@ public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService { public void onCreate() { super.onCreate(); mBinder = new LocalBinder(); - context.registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - }, exception -> FabricPrivacy.getInstance().logException(exception)) - ); - disposable.add(rxBus - .toObservable(EventAppExit.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - aapsLogger.debug(LTag.PUMP, "EventAppExit received"); - - if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - context.unregisterReceiver(receiver); - stopSelf(); - }, exception -> FabricPrivacy.getInstance().logException(exception)) - ); - } - - @Override - public void onDestroy() { - disposable.clear(); - super.onDestroy(); } public class LocalBinder extends Binder { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java index cb45d18bbd..8416794589 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java @@ -108,8 +108,6 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { @Inject ProfileFunction profileFunction; @Inject SP sp; - private CompositeDisposable disposable = new CompositeDisposable(); - private long lastHistoryFetched = 0; public DanaRv2ExecutionService() { @@ -125,33 +123,6 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { public void onCreate() { super.onCreate(); mBinder = new LocalBinder(); - context.registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - }, exception -> FabricPrivacy.getInstance().logException(exception)) - ); - disposable.add(rxBus - .toObservable(EventAppExit.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - aapsLogger.debug(LTag.PUMP, "EventAppExit received"); - - if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - context.getApplicationContext().unregisterReceiver(receiver); - stopSelf(); - }, exception -> FabricPrivacy.getInstance().logException(exception)) - ); - } - - @Override - public void onDestroy() { - disposable.clear(); - super.onDestroy(); } public void connect() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt index ad1d1054f6..05ff1cf463 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt @@ -37,6 +37,7 @@ import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.events.EventQueueChanged import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.OKDialog import info.nightscout.androidaps.utils.SetWarnColor import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.extensions.plusAssign @@ -86,13 +87,13 @@ class MedtronicFragment : DaggerFragment() { if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { startActivity(Intent(context, MedtronicHistoryActivity::class.java)) } else { - MedtronicUtil.displayNotConfiguredDialog(context) + displayNotConfiguredDialog() } } medtronic_refresh.setOnClickListener { if (!MedtronicUtil.getPumpStatus().verifyConfiguration()) { - MedtronicUtil.displayNotConfiguredDialog(context) + displayNotConfiguredDialog() } else { medtronic_refresh.isEnabled = false medtronicPumpPlugin.resetStatusState() @@ -108,7 +109,7 @@ class MedtronicFragment : DaggerFragment() { if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { startActivity(Intent(context, RileyLinkStatusActivity::class.java)) } else { - MedtronicUtil.displayNotConfiguredDialog(context) + displayNotConfiguredDialog() } } } @@ -247,6 +248,13 @@ class MedtronicFragment : DaggerFragment() { } } + private fun displayNotConfiguredDialog() { + context?.let { + OKDialog.show(it, resourceHelper.gs(R.string.combo_warning), + resourceHelper.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null) + } + } + // GUI functions @Synchronized fun updateGUI() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java index b2a5c97233..b4cebbb1cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java @@ -1,7 +1,6 @@ package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui; import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; @@ -43,8 +42,7 @@ public class MedtronicUIComm { public synchronized MedtronicUITask executeCommand(MedtronicCommandType commandType, Object... parameters) { - if (isLogEnabled()) - aapsLogger.warn(LTag.PUMP, "Execute Command: " + commandType.name()); + aapsLogger.warn(LTag.PUMP, "Execute Command: " + commandType.name()); MedtronicUITask task = new MedtronicUITask(commandType, parameters); @@ -78,7 +76,7 @@ public class MedtronicUIComm { // } // } - if (!task.isReceived() && isLogEnabled()) { + if (!task.isReceived()) { aapsLogger.warn(LTag.PUMP, "Reply not received for " + commandType); } @@ -112,9 +110,4 @@ public class MedtronicUIComm { public void startTunning() { RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump); } - - private boolean isLogEnabled() { - return L.isEnabled(L.PUMP); - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java index 17f7b03a17..218f328b26 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java @@ -61,10 +61,6 @@ public class RileyLinkMedtronicService extends RileyLinkService { } - public static MedtronicCommunicationManager getCommunicationManager() { - return instance.medtronicCommunicationManager; - } - @Override public void onCreate() { super.onCreate(); aapsLogger.debug(LTag.PUMPCOMM, "RileyLinkMedtronicService newly created"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java index 2ac9d4ef56..7f02a42d21 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java @@ -1,13 +1,10 @@ package info.nightscout.androidaps.plugins.pump.medtronic.util; -import android.content.Context; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.joda.time.LocalTime; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -15,8 +12,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; @@ -41,7 +36,6 @@ import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange; import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService; -import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.resources.ResourceHelper; /** @@ -532,9 +526,4 @@ public class MedtronicUtil extends RileyLinkUtil { } - public static void displayNotConfiguredDialog(Context context) { - OKDialog.show(context, MainApp.gs(R.string.combo_warning), - MainApp.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null); - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index 6615e47e22..9704d82f43 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData; import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; import info.nightscout.androidaps.utils.DateUtil; @@ -571,7 +572,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface // return true if new record is created @Override public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo, boolean allowUpdate) { - boolean medtronicPump = MedtronicUtil.isMedtronicPump(); + boolean medtronicPump = activePlugin.getActivePump() instanceof MedtronicPumpPlugin; getAapsLogger().debug(MedtronicHistoryData.doubleBolusDebug, LTag.DATATREATMENTS, "DoubleBolusDebug: addToHistoryTreatment::isMedtronicPump={} " + medtronicPump); diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/BTReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/BTReceiver.kt new file mode 100644 index 0000000000..a3b54e924e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/BTReceiver.kt @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.receivers + +import android.bluetooth.BluetoothDevice +import android.content.Context +import android.content.Intent +import dagger.android.DaggerBroadcastReceiver +import info.nightscout.androidaps.events.EventBTChange +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import javax.inject.Inject + +class BTReceiver : DaggerBroadcastReceiver() { + @Inject lateinit var rxBus: RxBusWrapper + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + val device : BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) + + when (intent.action) { + BluetoothDevice.ACTION_ACL_CONNECTED -> + rxBus.send(EventBTChange(EventBTChange.Change.CONNECT, device.name)) + BluetoothDevice.ACTION_ACL_DISCONNECTED -> + rxBus.send(EventBTChange(EventBTChange.Change.DISCONNECT, device.name)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt index 5164e8388e..3912b48433 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt @@ -59,6 +59,7 @@ class SWDefinition @Inject constructor( private val nsClientPlugin: NSClientPlugin, private val nsProfilePlugin: NSProfilePlugin, private val protectionCheck: ProtectionCheck, + private val importExportPrefs: ImportExportPrefs, private val androidPermission: AndroidPermission ) { @@ -160,8 +161,8 @@ class SWDefinition @Inject constructor( .add(SWBreak(injector)) .add(SWButton(injector) .text(R.string.nav_import) - .action(Runnable { ImportExportPrefs.importSharedPreferences(activity) })) - .visibility(SWValidator { ImportExportPrefs.file.exists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .action(Runnable { importExportPrefs.importSharedPreferences(activity) })) + .visibility(SWValidator { importExportPrefs.prefsFileExists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) private val screenNsClient = SWScreen(injector, R.string.nsclientinternal_title) .skippable(true) .add(SWInfotext(injector) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt index 285b3aaa10..186b81ae36 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt @@ -4,17 +4,10 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.DialogInterface -import android.os.Handler import android.os.SystemClock import android.text.Spanned -import android.view.LayoutInflater -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.view.ContextThemeWrapper -import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper object OKDialog { @SuppressLint("InflateParams") @@ -23,11 +16,9 @@ object OKDialog { fun show(context: Context, title: String, message: String, runnable: Runnable? = null) { var notEmptytitle = title if (notEmptytitle.isEmpty()) notEmptytitle = context.getString(R.string.message) - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = notEmptytitle - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) - .setCustomTitle(titleLayout) + + AlertDialogHelper.Builder(context) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, notEmptytitle)) .setMessage(message) .setPositiveButton(context.getString(R.string.ok)) { dialog: DialogInterface, _: Int -> dialog.dismiss() @@ -38,23 +29,15 @@ object OKDialog { .setCanceledOnTouchOutside(false) } - fun runOnUiThread(theRunnable: Runnable?) { - @Suppress("DEPRECATION") - val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) - theRunnable?.let { mainHandler.post(it) } - } - @SuppressLint("InflateParams") @JvmStatic @JvmOverloads fun show(activity: Activity, title: String, message: Spanned, runnable: Runnable? = null) { var notEmptytitle = title if (notEmptytitle.isEmpty()) notEmptytitle = activity.getString(R.string.message) - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = notEmptytitle - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) - .setCustomTitle(titleLayout) + + AlertDialogHelper.Builder(activity) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, notEmptytitle)) .setMessage(message) .setPositiveButton(activity.getString(R.string.ok)) { dialog: DialogInterface, _: Int -> dialog.dismiss() @@ -79,12 +62,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(activity: Activity, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + AlertDialogHelper.Builder(activity) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -103,12 +83,9 @@ object OKDialog { @SuppressLint("InflateParams") @JvmStatic fun showConfirmation(activity: Activity, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + AlertDialogHelper.Builder(activity) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -133,12 +110,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(context: Context, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + AlertDialogHelper.Builder(context) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -164,12 +138,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(context: Context, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + AlertDialogHelper.Builder(context) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -188,12 +159,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(context: Context, title: String, message: String, ok: DialogInterface.OnClickListener?, cancel: DialogInterface.OnClickListener? = null) { - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + AlertDialogHelper.Builder(context) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> dialog.dismiss() SystemClock.sleep(100) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java b/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java index 7822e8a80f..26062011f6 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java @@ -1,26 +1,94 @@ package info.nightscout.androidaps.utils; +import android.annotation.SuppressLint; import android.content.Context; import android.media.MediaPlayer; import android.os.Handler; import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.DrawableRes; +import androidx.appcompat.view.ContextThemeWrapper; + import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; public class ToastUtils { + + public static class Long { + + public static void warnToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_warn, false); + } + + public static void infoToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_info,false); + } + + public static void okToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_check,false); + } + + public static void errorToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_error,false); + } + } + public static void showToastInUiThread(final Context ctx, final int stringId) { showToastInUiThread(ctx, MainApp.gs(stringId)); } + public static void warnToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_warn, true); + } + + public static void infoToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_info, true); + } + + public static void okToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_check, true); + } + + public static void errorToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_error, true); + } + + public static void graphicalToast(final Context ctx, final String string, @DrawableRes int iconId) { + graphicalToast(ctx, string, iconId, true); + } + + @SuppressLint("InflateParams") + public static void graphicalToast(final Context ctx, final String string, @DrawableRes int iconId, boolean isShort) { + Handler mainThread = new Handler(Looper.getMainLooper()); + mainThread.post(() -> { + View toastRoot =LayoutInflater.from(new ContextThemeWrapper(ctx, R.style.AppTheme)).inflate(R.layout.toast, null); + TextView toastMessage = toastRoot.findViewById(android.R.id.message); + toastMessage.setText(string); + + ImageView toastIcon = toastRoot.findViewById(android.R.id.icon); + toastIcon.setImageResource(iconId); + + Toast toast = new Toast(ctx); + toast.setDuration(isShort ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG); + toast.setView(toastRoot); + toast.show(); + }); + } + public static void showToastInUiThread(final Context ctx, final String string) { Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(() -> Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show()); + mainThread.post(() -> { + Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show(); + }); } public static void showToastInUiThread(final Context ctx, final RxBusWrapper rxBus, diff --git a/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt b/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt index 99c4a4af31..ad57e519da 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt @@ -1,6 +1,8 @@ package info.nightscout.androidaps.utils +import android.os.Handler import android.view.View +import info.nightscout.androidaps.MainApp /** * Created by adrian on 2019-12-20. @@ -8,3 +10,7 @@ import android.view.View fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE +fun runOnUiThread(theRunnable: Runnable?) { + val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) + theRunnable?.let { mainHandler.post(it) } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt new file mode 100644 index 0000000000..afc5491652 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.LayoutRes +import androidx.annotation.StyleRes +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.view.ContextThemeWrapper +import info.nightscout.androidaps.R + +object AlertDialogHelper { + + @Suppress("FunctionName") + fun Builder(context: Context, @StyleRes themeResId: Int = R.style.AppTheme) = + AlertDialog.Builder(ContextThemeWrapper(context, themeResId)) + + fun buildCustomTitle(context: Context, title: String, + @DrawableRes iconResource: Int = R.drawable.ic_check_while_48dp, + @StyleRes themeResId: Int = R.style.AppTheme, + @LayoutRes layoutResource: Int = R.layout.dialog_alert_custom): View? { + val titleLayout = LayoutInflater.from(ContextThemeWrapper(context, themeResId)).inflate(layoutResource, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(iconResource) + return titleLayout + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt new file mode 100644 index 0000000000..d5b6900386 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt @@ -0,0 +1,138 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.graphics.Color +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.webkit.WebView +import android.widget.Button +import android.widget.ImageView +import android.widget.TableLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.annotation.StyleRes +import androidx.appcompat.view.ContextThemeWrapper +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.general.maintenance.formats.Prefs +import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.runOnUiThread +import java.util.* + +object PrefImportSummaryDialog { + + @SuppressLint("InflateParams") + fun showSummary(context: Context, importOk: Boolean, importPossible: Boolean, prefs: Prefs, ok: (() -> Unit)?, cancel: (() -> Unit)? = null) { + + @StyleRes val theme: Int = if (importOk) R.style.AppTheme else { + if (importPossible) R.style.AppThemeWarningDialog else R.style.AppThemeErrorDialog + } + + @StringRes val messageRes: Int = if (importOk) R.string.check_preferences_before_import else { + if (importPossible) R.string.check_preferences_dangerous_import else R.string.check_preferences_cannot_import + } + + @DrawableRes val headerIcon: Int = if (importOk) R.drawable.ic_header_import else { + if (importPossible) R.drawable.ic_header_warning else R.drawable.ic_header_error + } + + val themedCtx = ContextThemeWrapper(context, theme) + + val innerLayout = LayoutInflater.from(themedCtx).inflate(R.layout.dialog_alert_import_summary, null) + val table = (innerLayout.findViewById(R.id.summary_table) as TableLayout) + val detailsBtn = (innerLayout.findViewById(R.id.summary_details_btn) as Button) + + var idx = 0 + val details = LinkedList() + + + for ((metaKey, metaEntry) in prefs.metadata) { + val rowLayout = LayoutInflater.from(themedCtx).inflate(R.layout.import_summary_item, null) + val label = (rowLayout.findViewById(R.id.summary_text) as TextView) + label.setText(metaKey.formatForDisplay(context, metaEntry.value)) + (rowLayout.findViewById(R.id.summary_icon) as ImageView).setImageResource(metaKey.icon) + (rowLayout.findViewById(R.id.status_icon) as ImageView).setImageResource(metaEntry.status.icon) + + if (metaEntry.status == PrefsStatus.WARN) label.setTextColor(themedCtx.getColor(R.color.metadataTextWarning)) + else if (metaEntry.status == PrefsStatus.ERROR) label.setTextColor(themedCtx.getColor(R.color.metadataTextError)) + + if (metaEntry.info != null) { + details.add("${context.getString(metaKey.label)}: ${metaEntry.value}
${metaEntry.info}") + rowLayout.isClickable = true + rowLayout.setOnClickListener { + val msg = "[${context.getString(metaKey.label)}] ${metaEntry.info}" + when (metaEntry.status) { + PrefsStatus.WARN -> ToastUtils.Long.warnToast(context, msg) + PrefsStatus.ERROR -> ToastUtils.Long.errorToast(context, msg) + else -> ToastUtils.Long.infoToast(context, msg) + } + } + } else { + rowLayout.isClickable = true + rowLayout.setOnClickListener { ToastUtils.Long.infoToast(context, context.getString(metaKey.label)) } + } + + table.addView(rowLayout, idx++) + } + + if (details.size > 0) { + detailsBtn.visibility = View.VISIBLE + detailsBtn.setOnClickListener { + val detailsLayout = LayoutInflater.from(context).inflate(R.layout.import_summary_details, null) + val wview = detailsLayout.findViewById(R.id.details_webview) as WebView + wview.loadData("" + details.joinToString("
"), "text/html; charset=utf-8", "utf-8"); + wview.setBackgroundColor(Color.TRANSPARENT); + wview.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null); + + AlertDialogHelper.Builder(context, R.style.AppTheme) + .setCustomTitle(AlertDialogHelper.buildCustomTitle( + context, + context.getString(R.string.check_preferences_details_title), + R.drawable.ic_header_log, + R.style.AppTheme)) + .setView(detailsLayout) + .setPositiveButton(android.R.string.ok) { dialogInner: DialogInterface, _: Int -> + dialogInner.dismiss() + } + .show() + } + } + + val builder = AlertDialogHelper.Builder(context, theme) + .setMessage(context.getString(messageRes)) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(R.string.nav_import), headerIcon, theme)) + .setView(innerLayout) + + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (cancel != null) { + runOnUiThread(Runnable { + cancel() + }) + } + } + + if (importPossible) { + builder.setPositiveButton( + if (importOk) R.string.check_preferences_import_btn else R.string.check_preferences_import_anyway_btn + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (ok != null) { + runOnUiThread(Runnable { + ok() + }) + } + } + } + + val dialog = builder.show() + dialog.setCanceledOnTouchOutside(false) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt new file mode 100644 index 0000000000..9c1fbf37aa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt @@ -0,0 +1,49 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.annotation.DrawableRes +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.runOnUiThread + +object TwoMessagesAlertDialog { + + @SuppressLint("InflateParams") + fun showAlert(context: Context, title: String, message: String, secondMessage: String, ok: (() -> Unit)?, cancel: (() -> Unit)? = null, @DrawableRes icon: Int? = null) { + + val secondMessageLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_two_messages, null) + (secondMessageLayout.findViewById(R.id.password_prompt_title) as TextView).text = secondMessage + + val dialog = AlertDialogHelper.Builder(context) + .setMessage(message) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title, icon + ?: R.drawable.ic_check_while_48dp)) + .setView(secondMessageLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (ok != null) { + runOnUiThread(Runnable { + ok() + }) + } + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (cancel != null) { + runOnUiThread(Runnable { + cancel() + }) + } + } + .show() + dialog.setCanceledOnTouchOutside(false) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt new file mode 100644 index 0000000000..cd0654f3bf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt @@ -0,0 +1,49 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.os.SystemClock +import androidx.annotation.StringRes +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.runOnUiThread + +// if you need error dialog - duplicate to ErrorDialog and make it and use: AppThemeErrorDialog & R.drawable.ic_header_error instead + +object WarningDialog { + + @SuppressLint("InflateParams") + @JvmStatic + @JvmOverloads + fun showWarning(context: Context, title: String, message: String, @StringRes positiveButton: Int = -1, ok: (() -> Unit)? = null, cancel: (() -> Unit)? = null) { + + val builder = AlertDialogHelper.Builder(context, R.style.AppThemeWarningDialog) + .setMessage(message) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title, R.drawable.ic_header_warning, R.style.AppThemeWarningDialog)) + .setNegativeButton(R.string.dismiss) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (cancel != null) { + runOnUiThread(Runnable { + cancel() + }) + } + } + + if (positiveButton != -1) { + builder.setPositiveButton(positiveButton) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (ok != null) { + runOnUiThread(Runnable { + ok() + }) + } + } + } + + val dialog = builder.show() + dialog.setCanceledOnTouchOutside(true) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt index 65f1e76e44..1907ac01b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt @@ -1,20 +1,16 @@ package info.nightscout.androidaps.utils.protection import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.Context import android.os.Build import android.view.LayoutInflater import android.view.View import android.widget.EditText -import android.widget.ImageView -import android.widget.TextView import androidx.annotation.StringRes -import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.FragmentActivity import info.nightscout.androidaps.R import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper import info.nightscout.androidaps.utils.sharedPreferences.SP import javax.inject.Inject import javax.inject.Singleton @@ -26,41 +22,36 @@ val AUTOFILL_HINT_NEW_PASSWORD = "newPassword" class PasswordCheck @Inject constructor(val sp: SP) { @SuppressLint("InflateParams") - fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) { + fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) { val password = sp.getString(preference, "") if (password == "") { ok?.invoke("") return } - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = activity.getString(labelId) - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp) - - val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null) - - val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) + val alertDialogBuilder = AlertDialogHelper.Builder(context) alertDialogBuilder.setView(promptsView) val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val autoFillHintPasswordKind = activity.getString(preference) + val autoFillHintPasswordKind = context.getString(preference) userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}") userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES } alertDialogBuilder .setCancelable(false) - .setCustomTitle(titleLayout) - .setPositiveButton(activity.getString(R.string.ok)) { _, _ -> + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key)) + .setPositiveButton(context.getString(R.string.ok)) { _, _ -> val enteredPassword = userInput.text.toString() if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword) else { - ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword)) + ToastUtils.errorToast(context, context.getString(R.string.wrongpassword)) fail?.invoke() } } - .setNegativeButton(activity.getString(R.string.cancel) + .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> cancel?.invoke() dialog.cancel() @@ -72,12 +63,7 @@ class PasswordCheck @Inject constructor(val sp: SP) { @SuppressLint("InflateParams") fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)? = null, cancel: (()->Unit)? = null, clear: (()->Unit)? = null) { val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) - - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = context.getText(labelId) - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp) - - val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + val alertDialogBuilder = AlertDialogHelper.Builder(context) alertDialogBuilder.setView(promptsView) val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText @@ -90,20 +76,20 @@ class PasswordCheck @Inject constructor(val sp: SP) { alertDialogBuilder .setCancelable(false) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key)) .setPositiveButton(context.getString(R.string.ok)) { _, _ -> val enteredPassword = userInput.text.toString() if (enteredPassword.isNotEmpty()) { sp.putString(preference, CryptoUtil.hashPassword(enteredPassword)) - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set)) + ToastUtils.okToast(context, context.getString(R.string.password_set)) ok?.invoke(enteredPassword) } else { if (sp.contains(preference)) { sp.remove(preference) - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_cleared)) + ToastUtils.graphicalToast(context, context.getString(R.string.password_cleared), R.drawable.ic_toast_delete_confirm) clear?.invoke() } else { - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) + ToastUtils.warnToast(context, context.getString(R.string.password_not_changed)) cancel?.invoke() } } @@ -111,7 +97,7 @@ class PasswordCheck @Inject constructor(val sp: SP) { } .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) + ToastUtils.infoToast(context, context.getString(R.string.password_not_changed)) cancel?.invoke() dialog.cancel() } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt b/app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt new file mode 100644 index 0000000000..40be1e65c3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.utils.storage + +import java.io.File +import javax.inject.Singleton + +@Singleton +class FileStorage : Storage { + + override fun getFileContents(file: File): String { + return file.readText() + } + + override fun putFileContents(file: File, contents: String) { + file.writeText(contents) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt b/app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt new file mode 100644 index 0000000000..475cd2675e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.utils.storage + +import java.io.File + +// This may seems unnecessary abstraction - but it will simplify testing +interface Storage { + + fun getFileContents(file: File) : String + fun putFileContents(file: File, contents: String) + +} diff --git a/app/src/main/res/drawable/alert_border_error.xml b/app/src/main/res/drawable/alert_border_error.xml new file mode 100644 index 0000000000..d1bcae1348 --- /dev/null +++ b/app/src/main/res/drawable/alert_border_error.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/alert_border_warning.xml b/app/src/main/res/drawable/alert_border_warning.xml new file mode 100644 index 0000000000..c73a9517a5 --- /dev/null +++ b/app/src/main/res/drawable/alert_border_warning.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_header_error.xml b/app/src/main/res/drawable/ic_header_error.xml new file mode 100644 index 0000000000..09c29c1558 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_error.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_export.xml b/app/src/main/res/drawable/ic_header_export.xml new file mode 100644 index 0000000000..d59103ab07 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_export.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_import.xml b/app/src/main/res/drawable/ic_header_import.xml new file mode 100644 index 0000000000..e929fad69b --- /dev/null +++ b/app/src/main/res/drawable/ic_header_import.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_key.xml b/app/src/main/res/drawable/ic_header_key.xml new file mode 100644 index 0000000000..416cc50bd3 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_key.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_log.xml b/app/src/main/res/drawable/ic_header_log.xml new file mode 100644 index 0000000000..6ef0e68d81 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_log.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_warning.xml b/app/src/main/res/drawable/ic_header_warning.xml new file mode 100644 index 0000000000..3d7afd8ef3 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_warning.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_key_48dp.xml b/app/src/main/res/drawable/ic_key_48dp.xml deleted file mode 100644 index 813dc24d00..0000000000 --- a/app/src/main/res/drawable/ic_key_48dp.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_date.xml b/app/src/main/res/drawable/ic_meta_date.xml new file mode 100644 index 0000000000..190392aaa1 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_date.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_encryption.xml b/app/src/main/res/drawable/ic_meta_encryption.xml new file mode 100644 index 0000000000..597b35a77a --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_encryption.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_error.xml b/app/src/main/res/drawable/ic_meta_error.xml new file mode 100644 index 0000000000..e5d213f774 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_error.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_flavour.xml b/app/src/main/res/drawable/ic_meta_flavour.xml new file mode 100644 index 0000000000..a7fe7f3471 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_flavour.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_format.xml b/app/src/main/res/drawable/ic_meta_format.xml new file mode 100644 index 0000000000..2023a4934d --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_format.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_model.xml b/app/src/main/res/drawable/ic_meta_model.xml new file mode 100644 index 0000000000..eca3e69c59 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_model.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_name.xml b/app/src/main/res/drawable/ic_meta_name.xml new file mode 100644 index 0000000000..ad120e037a --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_name.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_ok.xml b/app/src/main/res/drawable/ic_meta_ok.xml new file mode 100644 index 0000000000..663897c160 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_ok.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_meta_version.xml b/app/src/main/res/drawable/ic_meta_version.xml new file mode 100644 index 0000000000..ddf95f2fa9 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_version.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_warning.xml b/app/src/main/res/drawable/ic_meta_warning.xml new file mode 100644 index 0000000000..9cf5ce2c36 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_warning.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_check.xml b/app/src/main/res/drawable/ic_toast_check.xml new file mode 100644 index 0000000000..26b9f05b49 --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_check.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_delete_confirm.xml b/app/src/main/res/drawable/ic_toast_delete_confirm.xml new file mode 100644 index 0000000000..de6371045a --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_delete_confirm.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_error.xml b/app/src/main/res/drawable/ic_toast_error.xml new file mode 100644 index 0000000000..fb49272c0a --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_error.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_info.xml b/app/src/main/res/drawable/ic_toast_info.xml new file mode 100644 index 0000000000..9d54be6827 --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_info.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_warn.xml b/app/src/main/res/drawable/ic_toast_warn.xml new file mode 100644 index 0000000000..8864b16500 --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_warn.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_border_ok.xml b/app/src/main/res/drawable/toast_border_ok.xml new file mode 100644 index 0000000000..1c62848b31 --- /dev/null +++ b/app/src/main/res/drawable/toast_border_ok.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/app/src/main/res/layout/dialog_alert_custom.xml b/app/src/main/res/layout/dialog_alert_custom.xml index eea5fc6882..f006387d28 100644 --- a/app/src/main/res/layout/dialog_alert_custom.xml +++ b/app/src/main/res/layout/dialog_alert_custom.xml @@ -1,4 +1,8 @@ + + android:layout_height="wrap_content" + android:tint="?dialogTitleIconTint" /> + android:textAppearance="?android:attr/textAppearanceLarge" + android:textColor="?dialogTitleColor" /> + - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_alert_import_summary.xml b/app/src/main/res/layout/dialog_alert_import_summary.xml new file mode 100644 index 0000000000..fb6e5a0708 --- /dev/null +++ b/app/src/main/res/layout/dialog_alert_import_summary.xml @@ -0,0 +1,30 @@ + + + + + +