diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 2622312eb..bd87a6ce7 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -72,16 +72,16 @@ jobs: sha256sum output/remote.aab > output/remote.aab.sha256 ls -alR output -# - name: publish AAB to playstore -# if: github.ref == 'refs/heads/master' -# uses: dogi/upload-google-play@v1.1.3 -# with: -# serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} -# packageName: io.treehouses.remote -# releaseFiles: output/remote.aab -# track: internal -# releaseName: "${{ env.VERSION }}" -# status: completed + - name: publish AAB to playstore + if: github.ref == 'refs/heads/master' + uses: dogi/upload-google-play@v1.1.3 + with: + serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: io.treehouses.remote + releaseFiles: output/remote.aab + track: internal + releaseName: "${{ env.VERSION }}" + status: completed # - name: mobile security framework # run: | diff --git a/app/src/main/kotlin/io/treehouses/remote/InitialActivity.kt b/app/src/main/kotlin/io/treehouses/remote/InitialActivity.kt index d0f84a98c..cddd7bf36 100644 --- a/app/src/main/kotlin/io/treehouses/remote/InitialActivity.kt +++ b/app/src/main/kotlin/io/treehouses/remote/InitialActivity.kt @@ -1,5 +1,6 @@ package io.treehouses.remote +import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -10,7 +11,10 @@ import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.preference.PreferenceManager @@ -136,10 +140,15 @@ class InitialActivity : BaseInitialActivity() { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == 99) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this@InitialActivity, "Permissions Granted", Toast.LENGTH_SHORT).show() - } //TODO re-request + when (requestCode) { + REQUEST_LOCATION_PERMISSION_FOR_COMMUNITY -> { + if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { + preferences?.edit()?.putBoolean("send_log", true)?.apply() + goToCommunity() + } else { + Toast.makeText(this, "Permission denied. Cannot proceed to community features.", Toast.LENGTH_SHORT).show() + } + } } } @@ -167,27 +176,49 @@ class InitialActivity : BaseInitialActivity() { FeedbackDialogFragment().show(supportFragmentManager.beginTransaction(), "feedbackDialogFragment") } R.id.action_community -> { - preferences = PreferenceManager.getDefaultSharedPreferences(this) - val v = layoutInflater.inflate(R.layout.alert_log_map, null) - if (!preferences?.getBoolean("send_log", false)!!) { - val builder = DialogUtils.createAlertDialog(this@InitialActivity, "Sharing is Caring.", "The community map is only available with data sharing. " + - "Please enable data sharing to access this feature.", v).setCancelable(false) - DialogUtils.createAdvancedDialog(builder, Pair("Enable Data Sharing", "Cancel"), { - preferences!!.edit().putBoolean("send_log", true).apply() - goToCommunity() - }, {MainApplication.showLogDialog = false }) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + dataSharing() + } else { + preferences?.edit()?.putBoolean("send_log", true)?.apply() + goToCommunity() } - else { goToCommunity() } } } return super.onOptionsItemSelected(item) } + fun dataSharing() { + preferences = PreferenceManager.getDefaultSharedPreferences(this) + val v = layoutInflater.inflate(R.layout.alert_log_map, null) + if (preferences?.getBoolean("send_log", false) == false) { + val builder = DialogUtils.createAlertDialog(this@InitialActivity, + "Sharing is Caring", + "To enable the community map, Treehouses needs to collect some data and your approximate location. Would you like to enable data sharing?", v) + .setCancelable(false) + DialogUtils.createAdvancedDialog(builder, Pair("Enable Data Sharing", "Cancel"), { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), REQUEST_LOCATION_PERMISSION_FOR_COMMUNITY) + }, {MainApplication.showLogDialog = false }) + } else { + goToCommunity() + } + } + private fun goToCommunity() { openCallFragment(CommunityFragment()) title = getString(R.string.action_community) } + private fun showLocationPermissionDisclosureForCommunity() { + AlertDialog.Builder(this) + .setTitle("Location & GPS Usage") + .setMessage("This app collects location data to create a community map and understand user distribution. " + + "This helps us improve our services. To continue, please enable GPS.") + .setPositiveButton("Ok") { _, _ -> + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), REQUEST_LOCATION_PERMISSION_FOR_COMMUNITY) + } + .setNegativeButton("No", null).show() + } + fun changeAppBar() { mActionBarDrawerToggle = ActionBarDrawerToggle(this, bind.drawerLayout, findViewById(R.id.toolbar), 0, 0) mActionBarDrawerToggle.toolbarNavigationClickListener = View.OnClickListener { @@ -208,4 +239,8 @@ class InitialActivity : BaseInitialActivity() { mActionBarDrawerToggle.isDrawerIndicatorEnabled = true bind.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) } + + companion object { + private const val REQUEST_LOCATION_PERMISSION_FOR_COMMUNITY = 2 + } } diff --git a/app/src/main/kotlin/io/treehouses/remote/bases/PermissionActivity.kt b/app/src/main/kotlin/io/treehouses/remote/bases/PermissionActivity.kt index 75c9d1119..95a2d7b28 100644 --- a/app/src/main/kotlin/io/treehouses/remote/bases/PermissionActivity.kt +++ b/app/src/main/kotlin/io/treehouses/remote/bases/PermissionActivity.kt @@ -38,6 +38,10 @@ abstract class PermissionActivity : AppCompatActivity() { } fun requestPermission() { + proceedWithoutLocationPermission() + } + + private fun proceedWithLocationPermission() { val permissionsToRequest = mutableListOf( Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.BLUETOOTH, @@ -56,11 +60,29 @@ abstract class PermissionActivity : AppCompatActivity() { } } + private fun proceedWithoutLocationPermission() { + val permissionsToRequest = mutableListOf( + Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.BLUETOOTH, + Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN + ) + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU && !checkPermission(Manifest.permission.POST_NOTIFICATIONS)) { + permissionsToRequest.add(Manifest.permission.POST_NOTIFICATIONS) + } + + if (permissionsToRequest.any { !checkPermission(it) }) { + ActivityCompat.requestPermissions(this, permissionsToRequest.toTypedArray(), PERMISSION_REQUEST_WIFI) + } + } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_WIFI) { if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { - statusCheck() + if (checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { + statusCheck() + } } } } diff --git a/app/src/main/kotlin/io/treehouses/remote/fragments/SettingsFragment.kt b/app/src/main/kotlin/io/treehouses/remote/fragments/SettingsFragment.kt index fa32276c2..89ae6577c 100644 --- a/app/src/main/kotlin/io/treehouses/remote/fragments/SettingsFragment.kt +++ b/app/src/main/kotlin/io/treehouses/remote/fragments/SettingsFragment.kt @@ -1,22 +1,33 @@ package io.treehouses.remote.fragments +import android.Manifest import android.content.Context +import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.content.pm.PackageManager import android.os.Bundle import android.view.View +import android.widget.Toast +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import io.treehouses.remote.fragments.preferencefragments.GeneralPreferenceFragment -import io.treehouses.remote.fragments.preferencefragments.UserCustomizationPreferenceFragment +import androidx.preference.PreferenceManager +import androidx.preference.SwitchPreferenceCompat +import io.treehouses.remote.MainApplication import io.treehouses.remote.R import io.treehouses.remote.callback.HomeInteractListener +import io.treehouses.remote.fragments.preferencefragments.GeneralPreferenceFragment +import io.treehouses.remote.fragments.preferencefragments.UserCustomizationPreferenceFragment +import io.treehouses.remote.utils.DialogUtils import io.treehouses.remote.utils.SettingsUtils -class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener { +class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private var preferenceChangeListener: OnSharedPreferenceChangeListener? = null private lateinit var listener : HomeInteractListener + private lateinit var preferences: SharedPreferences + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.app_preferences, rootKey) val general = findPreference("general") @@ -25,12 +36,16 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic SettingsUtils.setClickListener(this, showBluetoothFile) SettingsUtils.setClickListener(this, general) SettingsUtils.setClickListener(this, usercustomization) + + val sendLogPreference = findPreference("send_log") + sendLogPreference?.onPreferenceChangeListener = this } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.windowBackground)) setDivider(null) + preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) } override fun onResume() { @@ -68,4 +83,58 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic listener = if (context is HomeInteractListener) context else throw Exception("Context does not implement HomeInteractListener") } -} \ No newline at end of file + + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + if (preference.key == "send_log") { + val value = newValue as? Boolean ?: false + if (value) { + dataSharing() + } else { + preferences.edit()?.putBoolean("send_log", false)?.apply() + } + (preference as? SwitchPreferenceCompat)?.isChecked = preferences.getBoolean("send_log", false) + } + return true + } + + + fun dataSharing() { + val v = layoutInflater.inflate(R.layout.alert_log, null) + val emoji = String(Character.toChars(0x1F60A)) + val builder = DialogUtils.createAlertDialog(activity, + "Sharing is Caring $emoji", + "To make Treehouses better, we'd like to collect some data and your approximate location. Would you like to enable data sharing?", v) + .setCancelable(false) + DialogUtils.createAdvancedDialog(builder, Pair("Enable Data Sharing", "Cancel"), { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), + REQUEST_LOCATION_PERMISSION_FOR_ACTIVITY_COLLECTION + ) + } else { + preferences.edit()?.putBoolean("send_log", true)?.apply() + } + }, { + val sendLogPreference = findPreference("send_log") + sendLogPreference?.isChecked = false + preferences.edit()?.putBoolean("send_log", false)?.apply() + MainApplication.showLogDialog = false + }) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + when (requestCode) { + REQUEST_LOCATION_PERMISSION_FOR_ACTIVITY_COLLECTION -> { + if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { + preferences.edit()?.putBoolean("send_log", true)?.apply() + } else { + Toast.makeText(context, "Permission denied. We won't collect your activities", Toast.LENGTH_SHORT).show() + } + } + } + } + + companion object { + private const val REQUEST_LOCATION_PERMISSION_FOR_ACTIVITY_COLLECTION = 2 + } +} diff --git a/app/src/main/kotlin/io/treehouses/remote/ui/home/BaseHomeFragment.kt b/app/src/main/kotlin/io/treehouses/remote/ui/home/BaseHomeFragment.kt index 9f643b96f..16b38701e 100644 --- a/app/src/main/kotlin/io/treehouses/remote/ui/home/BaseHomeFragment.kt +++ b/app/src/main/kotlin/io/treehouses/remote/ui/home/BaseHomeFragment.kt @@ -1,17 +1,22 @@ package io.treehouses.remote.ui.home +import android.Manifest import android.app.AlertDialog import android.content.ActivityNotFoundException import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences +import android.content.pm.PackageManager import android.graphics.drawable.AnimationDrawable import android.net.Uri import android.view.View import android.widget.ImageView import android.widget.Toast +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels import androidx.preference.PreferenceManager +import io.treehouses.remote.InitialActivity import io.treehouses.remote.IntroActivity import io.treehouses.remote.MainApplication import io.treehouses.remote.R @@ -81,9 +86,32 @@ open class BaseHomeFragment : BaseFragment() { if (lastDialogShown < date.timeInMillis && !preferences.getBoolean("send_log", false)) { if (connectionCount >= 3) { preferences.edit().putLong("last_dialog_shown", Calendar.getInstance().timeInMillis).apply() - val builder = DialogUtils.createAlertDialog(activity, "Sharing is Caring $emoji", "Treehouses wants to collect your activities. " + - "Do you like to share it? It will help us to improve.", v).setCancelable(false) - DialogUtils.createAdvancedDialog(builder, Pair("Continue", "Cancel"), { preferences.edit().putBoolean("send_log", true).apply() }, { MainApplication.showLogDialog = false }) + val builder = DialogUtils.createAlertDialog(activity, + "Sharing is Caring $emoji", + "Treehouses wants to collect your activities. Do you like to share it? It will help us to improve.", v) + .setCancelable(false) + DialogUtils.createAdvancedDialog(builder, Pair("Continue", "Cancel"), { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), + REQUEST_LOCATION_PERMISSION_FOR_ACTIVITY_COLLECTION + ) + } else { + preferences.edit()?.putBoolean("send_log", true)?.apply() + } + }, { MainApplication.showLogDialog = false }) + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + when (requestCode) { + REQUEST_LOCATION_PERMISSION_FOR_ACTIVITY_COLLECTION -> { + if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { + preferences?.edit()?.putBoolean("send_log", true)?.apply() + } else { + Toast.makeText(context, "Permission denied. We won't collect your activities", Toast.LENGTH_SHORT).show() + } } } } @@ -271,4 +299,8 @@ open class BaseHomeFragment : BaseFragment() { alertDialog.window!!.setBackgroundDrawableResource(android.R.color.transparent) alertDialog.show() } + + companion object { + private const val REQUEST_LOCATION_PERMISSION_FOR_ACTIVITY_COLLECTION = 2 + } } \ No newline at end of file diff --git a/app/src/main/res/xml/app_preferences.xml b/app/src/main/res/xml/app_preferences.xml index f2f3c269b..ac98689f3 100644 --- a/app/src/main/res/xml/app_preferences.xml +++ b/app/src/main/res/xml/app_preferences.xml @@ -1,68 +1,78 @@ - + - + android:layout="@layout/custom_pref_whole_expandable" + android:title="General" /> - + + android:title="Share Data" /> - + - + android:layout="@layout/custom_pref_whole_expandable" + android:title="User Customization" /> - + - + - + - + android:layout="@layout/custom_pref_bottom" + android:title="Report an Issue"> + - + + android:layout="@layout/custom_pref_top" + android:title="View Bluetooth Server File" /> + android:layout="@layout/custom_pref_bottom" + android:summary="Prompt for replacing the Bluetooth server on the Raspberry Pi with the one that is compatible with the one that is on the Remote." + android:title="Sync Bluetooth File with Local File" />