Skip to content
This repository has been archived by the owner on Aug 19, 2023. It is now read-only.

Commit

Permalink
Added a launcher widget to toggle all locks.
Browse files Browse the repository at this point in the history
  • Loading branch information
d4rken committed Aug 27, 2018
1 parent 6d2bf5b commit 9d5c325
Show file tree
Hide file tree
Showing 16 changed files with 372 additions and 16 deletions.
14 changes: 14 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@

<service android:name="eu.thedarken.wldonate.main.core.service.LockService"/>

<receiver
android:name="eu.thedarken.wldonate.main.core.widget.ToggleWidgetProvider"
android:icon="@drawable/ic_launcher_splash_bulb"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="${applicationId}.widget.ACTION_UPDATE_WIDGET_FROM_SERVICE"/>
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_toggle_info"/>
</receiver>

<meta-data
android:name="com.bugsnag.android.API_KEY"
android:value="${apikey_bugsnag}"/>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/eu/thedarken/wldonate/AndroidModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.PowerManager
import android.preference.PreferenceManager
import dagger.Module
import dagger.Provides
import java.io.File


@Module
Expand Down Expand Up @@ -40,4 +41,9 @@ class AndroidModule(private val app: App) {
@Provides
@AppComponent.Scope
fun packageManager(@ApplicationContext context: Context): PackageManager = context.packageManager

@SharedPrefsDir
@Provides
internal fun sharedPrefsDir(@ApplicationContext context: Context): File = File(context.cacheDir.parent, "shared_prefs")

}
7 changes: 5 additions & 2 deletions app/src/main/java/eu/thedarken/wldonate/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import eu.thedarken.wldonate.common.timber.BugsnagErrorHandler
import eu.thedarken.wldonate.common.timber.BugsnagTree
import eu.thedarken.wldonate.main.core.GeneralSettings
import eu.thedarken.wldonate.main.core.service.ServiceController
import eu.thedarken.wldonate.main.core.widget.WidgetController
import timber.log.Timber
import javax.inject.Inject

Expand All @@ -26,7 +27,11 @@ open class App : Application(), HasManualActivityInjector, HasManualBroadcastRec
@Inject lateinit var appComponent: AppComponent
@Inject lateinit var receiverInjector: ComponentSource<BroadcastReceiver>
@Inject lateinit var serviceInjector: ComponentSource<Service>
// No touchy! Needs to be initialized such that they sub to the lock-controller.
@Suppress("unused")
@Inject lateinit var serviceController: ServiceController
@Suppress("unused")
@Inject lateinit var widgetController: WidgetController

@Inject lateinit var settings: GeneralSettings
@Inject lateinit var uuidToken: UUIDToken
Expand Down Expand Up @@ -67,8 +72,6 @@ open class App : Application(), HasManualActivityInjector, HasManualBroadcastRec
}

companion object {
internal val TAG = logTag("ExampleApp")

fun logTag(vararg tags: String): String {
val sb = StringBuilder()
for (i in tags.indices) {
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/eu/thedarken/wldonate/ReceiverBinderModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ import dagger.android.BroadcastReceiverKey
import dagger.multibindings.IntoMap
import eu.thedarken.wldonate.main.core.receiver.LockCommandReceiver
import eu.thedarken.wldonate.main.core.receiver.LockCommandReceiverComponent
import eu.thedarken.wldonate.main.core.widget.ToggleWidgetComponent
import eu.thedarken.wldonate.main.core.widget.ToggleWidgetProvider

@Module(subcomponents = arrayOf(LockCommandReceiverComponent::class))
@Module(subcomponents = arrayOf(
LockCommandReceiverComponent::class,
ToggleWidgetComponent::class
))
internal abstract class ReceiverBinderModule {

@Binds
@IntoMap
@BroadcastReceiverKey(LockCommandReceiver::class)
internal abstract fun exampleReceiver(impl: LockCommandReceiverComponent.Builder): AndroidInjector.Factory<out BroadcastReceiver>

@Binds
@IntoMap
@BroadcastReceiverKey(ToggleWidgetProvider::class)
internal abstract fun widgetReceiver(impl: ToggleWidgetComponent.Builder): AndroidInjector.Factory<out BroadcastReceiver>
}
8 changes: 8 additions & 0 deletions app/src/main/java/eu/thedarken/wldonate/SharedPrefsDir.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package eu.thedarken.wldonate

import javax.inject.Qualifier

@Qualifier
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class SharedPrefsDir
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package eu.thedarken.wldonate.main.core;

import android.content.Context;
import android.content.SharedPreferences;

import java.io.File;

import javax.inject.Inject;

import eu.thedarken.wldonate.AppComponent;
import eu.thedarken.wldonate.ApplicationContext;
import eu.thedarken.wldonate.SharedPrefsDir;
import timber.log.Timber;

@AppComponent.Scope
public class WidgetSettingsRepo {
private final Context context;
private final File sharedPrefsDir;

@Inject
public WidgetSettingsRepo(@SharedPrefsDir File sharedPrefsDir, @ApplicationContext Context context) {
this.sharedPrefsDir = sharedPrefsDir;
this.context = context;
}

private static String getWidgetPrefsFileName(int appWidgetId) {
return "widget_" + appWidgetId + "_prefs";
}

public SharedPreferences getSettings(int appWidgetId) {
return context.getSharedPreferences(getWidgetPrefsFileName(appWidgetId), Context.MODE_PRIVATE);
}

public void deleteSettings(int appWidgetId) {
File prefs = new File(sharedPrefsDir, getWidgetPrefsFileName(appWidgetId) + ".xml");
if (prefs.exists() && !prefs.delete()) Timber.e("Failed to delete prefs: %s", prefs.getPath());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.content.Intent
import android.telephony.TelephonyManager
import eu.darken.mvpbakery.injection.broadcastreceiver.HasManualBroadcastReceiverInjector
import eu.thedarken.wldonate.main.core.GeneralSettings
import eu.thedarken.wldonate.main.core.locks.Lock
import eu.thedarken.wldonate.main.core.locks.LockController
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
Expand All @@ -14,7 +15,8 @@ import javax.inject.Inject

class LockCommandReceiver : BroadcastReceiver() {
companion object {
@JvmStatic val ACTION_EXIT = "RELEASE_ALL_LOCKS"
@JvmStatic val ACTION_STOP = "eu.thedarken.wldonate.actions.RELEASE_LOCKS"
@JvmStatic val ACTION_TOGGLE = "eu.thedarken.wldonate.actions.TOGGLE_LOCKS"
}

@Inject lateinit var lockController: LockController
Expand All @@ -35,9 +37,12 @@ class LockCommandReceiver : BroadcastReceiver() {
return
}

if (intent.action!! == ACTION_EXIT) {
Timber.i("Manual exit...")
if (intent.action!! == ACTION_STOP) {
Timber.i("Stop request")
releaseAll()
} else if (intent.action!! == ACTION_TOGGLE) {
Timber.i("Toggle request.")
toggle()
} else if (intent.action!! == Intent.ACTION_BOOT_COMPLETED && settings.isAutostartBootEnabled() && !settings.isAutostartCallEnabled()) {
Timber.i("Reboot, restoring locks...")
acquireSaved()
Expand All @@ -54,19 +59,38 @@ class LockCommandReceiver : BroadcastReceiver() {
}

private fun releaseAll() {
val async = goAsync()
var async: PendingResult? = null
lockController.acquireExclusive(Collections.emptySet())
.doOnSubscribe { async = goAsync() }
.subscribeOn(Schedulers.computation())
.doFinally { async.finish() }
.doFinally { async?.finish() }
.subscribe()
}

private fun acquireSaved() {
val async = goAsync()
val desired = settings.getSavedLocks()
var async: PendingResult? = null
lockController.acquireExclusive(desired)
.doOnSubscribe { async = goAsync() }
.subscribeOn(Schedulers.computation())
.doFinally { async.finish() }
.doFinally { async?.finish() }
.subscribe()
}

private fun toggle() {
var async: PendingResult? = null
lockController.locksPub
.subscribeOn(Schedulers.computation())
.doOnSubscribe { async = goAsync() }
.compose { Lock.acquiredOnly(it) }
.firstOrError()
.flatMapCompletable {
return@flatMapCompletable when {
it.isEmpty() -> lockController.acquireExclusive(settings.getSavedLocks())
else -> lockController.acquireExclusive(Collections.emptySet())
}
}
.doFinally { async?.finish() }
.subscribe()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject


class LockService : SmartService() {
companion object {
@JvmStatic val NOTIFICATION_CHANNEL_ID: String = "core.notification.channel.status"
Expand All @@ -35,6 +36,7 @@ class LockService : SmartService() {

private lateinit var statusNotifBuilder: NotificationCompat.Builder
private var notifUpdateSub: Disposable = Disposables.disposed()
private var widgetUpdateSub: Disposable = Disposables.disposed()

override fun onCreate() {
(application as App).serviceInjector().inject(this)
Expand All @@ -56,14 +58,13 @@ class LockService : SmartService() {
statusNotifBuilder.setContentIntent(openPI)

val exitIntentBroadcast = Intent(this, LockCommandReceiver::class.java)
exitIntentBroadcast.action = "RELEASE_ALL_LOCKS"
exitIntentBroadcast.action = LockCommandReceiver.ACTION_STOP
val exitPI = PendingIntent.getBroadcast(this, NOTIFICATION_PI_ID_EXIT, exitIntentBroadcast, PendingIntent.FLAG_UPDATE_CURRENT)
statusNotifBuilder.addAction(NotificationCompat.Action.Builder(R.drawable.ic_pause_circle_filled_white_24dp, getString(R.string.action_pause), exitPI).build())

startForeground(NOTIFICATION_SERVICE_ID, statusNotifBuilder.build())
notifUpdateSub = lockController.locksPub
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread())
.subscribe {
var acquired = 0
it.values.forEach { lock: Lock -> if (lock.isAcquired()) acquired++ }
Expand All @@ -79,7 +80,10 @@ class LockService : SmartService() {

override fun onDestroy() {
notifUpdateSub.dispose()
widgetUpdateSub.dispose()
stopForeground(true)
// Sometimes stopForeground doesn't remove the notification, but just makes it removable
notificationManager.cancel(NOTIFICATION_SERVICE_ID)
super.onDestroy()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package eu.thedarken.wldonate.main.core.widget


import dagger.Subcomponent
import eu.darken.mvpbakery.injection.broadcastreceiver.BroadcastReceiverComponent

@ToggleWidgetComponent.Scope
@Subcomponent
interface ToggleWidgetComponent : BroadcastReceiverComponent<ToggleWidgetProvider> {

@Subcomponent.Builder
abstract class Builder : BroadcastReceiverComponent.Builder<ToggleWidgetProvider, ToggleWidgetComponent>()

@javax.inject.Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class Scope
}

0 comments on commit 9d5c325

Please sign in to comment.