diff --git a/app/build.gradle b/app/build.gradle index dbecdb5..f7794f1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,6 @@ dependencies { implementation 'androidx.preference:preference:' + versions.androidxPrefs implementation 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle kapt 'androidx.lifecycle:lifecycle-compiler:' + versions.lifecycle - implementation 'androidx.browser:browser:' + versions.androidxBrowser // Koin implementation 'org.koin:koin-androidx-scope:' + versions.koin diff --git a/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/main/MainActivity.kt b/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/main/MainActivity.kt index f39ca9b..4b9bf72 100644 --- a/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/main/MainActivity.kt +++ b/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/main/MainActivity.kt @@ -23,7 +23,6 @@ import android.content.Intent.ACTION_VIEW import android.content.Intent.EXTRA_STREAM import android.os.Bundle import android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION -import androidx.browser.customtabs.CustomTabsIntent import androidx.lifecycle.Observer import com.afollestad.assent.Permission.WRITE_EXTERNAL_STORAGE import com.afollestad.assent.askForPermissions @@ -31,8 +30,8 @@ import com.afollestad.inlineactivityresult.startActivityForResult import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.callbacks.onCancel import com.afollestad.materialdialogs.callbacks.onDismiss -import com.afollestad.materialdialogs.utils.MDUtil.resolveColor import com.afollestad.mnmlscreenrecord.R +import com.afollestad.mnmlscreenrecord.common.intent.UrlLauncher import com.afollestad.mnmlscreenrecord.common.misc.startActivity import com.afollestad.mnmlscreenrecord.common.misc.toUri import com.afollestad.mnmlscreenrecord.common.misc.toast @@ -67,7 +66,9 @@ import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.include_appbar.toolbar import kotlinx.android.synthetic.main.list_item_recording.view.thumbnail +import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import kotlinx.android.synthetic.main.include_appbar.app_toolbar as appToolbar import kotlinx.android.synthetic.main.include_appbar.toolbar_title as toolbarTitle @@ -75,6 +76,8 @@ import kotlinx.android.synthetic.main.include_appbar.toolbar_title as toolbarTit class MainActivity : DarkModeSwitchActivity(), OverlayExplanationCallback { private val viewModel by viewModel() + private val urlLauncher by inject { parametersOf(this) } + private val dataSource = emptySelectableDataSourceTyped().apply { onSelectionChange { @@ -191,7 +194,9 @@ class MainActivity : DarkModeSwitchActivity(), OverlayExplanationCallback { setOnMenuItemDebouncedClickListener { item -> when (item.itemId) { R.id.about -> AboutDialog.show(this@MainActivity) - R.id.provide_feedback -> viewUrl("https://github.com/afollestad/mnml/issues/new/choose") + R.id.provide_feedback -> urlLauncher.viewUrl( + "https://github.com/afollestad/mnml/issues/new/choose" + ) R.id.settings -> startActivity() R.id.share -> shareRecording(dataSource.getSelectedItems().single()) R.id.delete -> { @@ -260,19 +265,6 @@ class MainActivity : DarkModeSwitchActivity(), OverlayExplanationCallback { }) } - private fun viewUrl(url: String) { - val customTabsIntent = CustomTabsIntent.Builder() - .apply { - setToolbarColor(resolveColor(this@MainActivity, attr = R.attr.colorPrimary)) - } - .build() - try { - customTabsIntent.launchUrl(this, url.toUri()) - } catch (_: ActivityNotFoundException) { - toast(R.string.install_web_browser) - } - } - private fun checkForMediaProjectionAvailability() { try { Class.forName("android.media.projection.MediaProjectionManager") diff --git a/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsControlsFragment.kt b/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsControlsFragment.kt index d1b9708..677c2e1 100644 --- a/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsControlsFragment.kt +++ b/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsControlsFragment.kt @@ -21,7 +21,6 @@ import android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION import androidx.preference.SwitchPreference import com.afollestad.mnmlscreenrecord.R import com.afollestad.mnmlscreenrecord.common.misc.toUri -import com.afollestad.mnmlscreenrecord.common.permissions.PermissionChecker import com.afollestad.mnmlscreenrecord.common.prefs.PrefNames.PREF_ALWAYS_SHOW_CONTROLS import com.afollestad.mnmlscreenrecord.common.prefs.PrefNames.PREF_STOP_ON_SCREEN_OFF import com.afollestad.mnmlscreenrecord.common.prefs.PrefNames.PREF_STOP_ON_SHAKE @@ -34,8 +33,6 @@ import org.koin.core.qualifier.named /** @author Aidan Follestad (@afollestad) */ class SettingsControlsFragment : BaseSettingsFragment(), OverlayExplanationCallback { - - private val permissionChecker by inject() private val stopOnScreenOffPref by inject>(named(PREF_STOP_ON_SCREEN_OFF)) private val alwaysShowControlsPref by inject>( named(PREF_ALWAYS_SHOW_CONTROLS) diff --git a/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsRecordingFragment.kt b/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsRecordingFragment.kt index f360d06..d46dc17 100644 --- a/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsRecordingFragment.kt +++ b/app/src/main/java/com/afollestad/mnmlscreenrecord/ui/settings/sub/SettingsRecordingFragment.kt @@ -15,12 +15,17 @@ */ package com.afollestad.mnmlscreenrecord.ui.settings.sub +import android.content.Intent import android.content.pm.PackageManager.FEATURE_MICROPHONE import android.os.Bundle +import android.provider.Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS import androidx.preference.SwitchPreference import com.afollestad.assent.Permission.RECORD_AUDIO import com.afollestad.assent.runWithPermissions +import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.mnmlscreenrecord.R +import com.afollestad.mnmlscreenrecord.common.intent.UrlLauncher +import com.afollestad.mnmlscreenrecord.common.permissions.PermissionChecker import com.afollestad.mnmlscreenrecord.common.prefs.PrefNames.PREF_COUNTDOWN import com.afollestad.mnmlscreenrecord.common.prefs.PrefNames.PREF_RECORDINGS_FOLDER import com.afollestad.mnmlscreenrecord.common.prefs.PrefNames.PREF_RECORD_AUDIO @@ -30,11 +35,14 @@ import com.afollestad.mnmlscreenrecord.ui.settings.showNumberSelector import com.afollestad.mnmlscreenrecord.ui.settings.showOutputFolderSelector import com.afollestad.rxkprefs.Pref import org.koin.android.ext.android.inject +import org.koin.core.parameter.parametersOf import org.koin.core.qualifier.named /** @author Aidan Follestad (@afollestad) */ class SettingsRecordingFragment : BaseSettingsFragment() { + private val permissionChecker by inject() + private val urlLauncher by inject { parametersOf(activity!!) } private val countdownPref by inject>(named(PREF_COUNTDOWN)) private val recordAudioPref by inject>(named(PREF_RECORD_AUDIO)) internal val recordingsFolderPref by inject>(named(PREF_RECORDINGS_FOLDER)) @@ -48,6 +56,21 @@ class SettingsRecordingFragment : BaseSettingsFragment() { setupCountdownPref() setupRecordAudioPref() setupRecordingsFolderPref() + + findPreference("show_touches").setOnPreferenceClickListener { + if (permissionChecker.hasDeveloperOptions()) { + startActivity(Intent(ACTION_APPLICATION_DEVELOPMENT_SETTINGS)) + } else { + MaterialDialog(activity!!).show { + title(R.string.settings_show_touches_dev_options) + message(R.string.settings_show_touches_dev_options_desc) + positiveButton(R.string.settings_show_touches_dev_options_how) { + urlLauncher.viewUrl(HOW_TO_DEV_OPTIONS_URL) + } + } + } + true + } } private fun setupCountdownPref() { @@ -119,4 +142,9 @@ class SettingsRecordingFragment : BaseSettingsFragment() { } .attachLifecycle(this) } + + private companion object { + private const val HOW_TO_DEV_OPTIONS_URL = + "https://developer.android.com/studio/debug/dev-options" + } } diff --git a/app/src/main/res/values/strings_settings.xml b/app/src/main/res/values/strings_settings.xml index 9c7b7df..4218eb1 100644 --- a/app/src/main/res/values/strings_settings.xml +++ b/app/src/main/res/values/strings_settings.xml @@ -61,11 +61,17 @@ Recordings Folder Recordings are saved to \"%1$s\" - Show Touches + Show Taps - MNML targets the latest versions of Android and Google now restricts access to this - setting, unfortunately. You can manually enable it from your phone\'s settings. + MNML targets the latest versions of Android, which restricts access to this + setting. You can manually enable it from your phone\'s settings. + Developer Mode Needed + + You must put your phone into developer mode so that MNML can show you developer options. + Developer options allow you to enable \'Show Taps\' manually. + + Show Me How Controls diff --git a/app/src/main/res/xml/settings_recording.xml b/app/src/main/res/xml/settings_recording.xml index 323a748..831c13e 100644 --- a/app/src/main/res/xml/settings_recording.xml +++ b/app/src/main/res/xml/settings_recording.xml @@ -24,7 +24,6 @@ android:title="@string/setting_recordings_folder"/> RealUrlLauncher(activity) } bind UrlLauncher::class } diff --git a/common/src/main/java/com/afollestad/mnmlscreenrecord/common/intent/UrlLauncher.kt b/common/src/main/java/com/afollestad/mnmlscreenrecord/common/intent/UrlLauncher.kt new file mode 100644 index 0000000..67cd1b9 --- /dev/null +++ b/common/src/main/java/com/afollestad/mnmlscreenrecord/common/intent/UrlLauncher.kt @@ -0,0 +1,59 @@ +/** + * Designed and developed by Aidan Follestad (@afollestad) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afollestad.mnmlscreenrecord.common.intent + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.content.Intent.ACTION_VIEW +import androidx.annotation.AttrRes +import androidx.browser.customtabs.CustomTabsIntent +import com.afollestad.mnmlscreenrecord.common.R +import com.afollestad.mnmlscreenrecord.common.misc.toUri + +/** @author Aidan Follestad (@afollestad) */ +interface UrlLauncher { + fun viewUrl(url: String) +} + +class RealUrlLauncher( + private val currentActivity: Activity +) : UrlLauncher { + + override fun viewUrl(url: String) { + val customTabsIntent = CustomTabsIntent.Builder() + .setToolbarColor(resolveColor(R.attr.colorPrimary)) + .build() + try { + customTabsIntent.launchUrl(currentActivity, url.toUri()) + } catch (_: ActivityNotFoundException) { + val chooser = Intent.createChooser( + Intent(ACTION_VIEW) + .setData(url.toUri()), "View URL" + ) + currentActivity.startActivity(chooser) + } + } + + private fun resolveColor(@AttrRes attr: Int): Int { + val a = currentActivity.theme.obtainStyledAttributes(intArrayOf(attr)) + try { + return a.getColor(0, 0) + } finally { + a.recycle() + } + } +} diff --git a/common/src/main/java/com/afollestad/mnmlscreenrecord/common/permissions/PermissionChecker.kt b/common/src/main/java/com/afollestad/mnmlscreenrecord/common/permissions/PermissionChecker.kt index 08fee0a..f96bdb7 100644 --- a/common/src/main/java/com/afollestad/mnmlscreenrecord/common/permissions/PermissionChecker.kt +++ b/common/src/main/java/com/afollestad/mnmlscreenrecord/common/permissions/PermissionChecker.kt @@ -16,9 +16,14 @@ package com.afollestad.mnmlscreenrecord.common.permissions import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.annotation.SuppressLint +import android.annotation.TargetApi import android.app.Application import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.Build import android.provider.Settings +import android.provider.Settings.Global +import android.provider.Settings.Secure /** * An abstraction layer for checking common permission access. @@ -36,6 +41,11 @@ interface PermissionChecker { * Returns true if the app has permission to write external storage. */ fun hasStoragePermission(): Boolean + + /** + * Returns true if the user has enabled Developer mode. + */ + fun hasDeveloperOptions(): Boolean } /** @author Aidan Follestad (@afollestad) */ @@ -50,4 +60,21 @@ class RealPermissionChecker( override fun hasStoragePermission(): Boolean { return app.checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED } + + @Suppress("DEPRECATION") + @SuppressLint("ObsoleteSdkInt") + @TargetApi(17) + override fun hasDeveloperOptions(): Boolean { + return when { + Build.VERSION.SDK_INT == 16 -> Secure.getInt( + app.contentResolver, + Secure.DEVELOPMENT_SETTINGS_ENABLED, 0 + ) != 0 + Build.VERSION.SDK_INT >= 17 -> Secure.getInt( + app.contentResolver, + Global.DEVELOPMENT_SETTINGS_ENABLED, 0 + ) != 0 + else -> false + } + } }