Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
# with the server's order (pinned pages lost, 1.67.27/28). Field names stay
# stable across releases like Familie's.
-keep class com.gatecontrol.android.layout.** { <fields>; }
# Mail (plugin gatecontrol-mail): answers are read from the Gson tree
# (MailJson) and the offline copy keeps the server's raw JSON (MailCache),
# nothing reflective; the models keep their field names like the door's, in
# case they are ever (de)serialized by reflection. PluginAvailability.mail is
# covered by the familie rule above.
-keep class com.gatecontrol.android.mail.** { <fields>; }

# ── WireGuard ─────────────────────────────────────────────
-keep class com.wireguard.** { *; }
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@
</intent-filter>
</activity>

<!-- Mail: the browser's answer to "Mit Microsoft anmelden" of a mail
account (msauth://com.gatecontrol.client/<signature hash>, as
Microsoft registers Android apps). Exported for the browser, but
disabled until the server offers Mail (MailStartup); hands the
one-time code to the Mail screen, which holds the PKCE verifier. -->
<activity
android:name=".MailSigninActivity"
android:exported="true"
android:enabled="false"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/Theme.GateControl.TileAction">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="msauth"
android:host="com.gatecontrol.client" />
</intent-filter>
</activity>

<!-- Performs Quick Settings tile actions (connect incl. VPN consent,
disconnect). NOT exported: other apps must not be able to
toggle the VPN. -->
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/gatecontrol/android/GateControlApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ class GateControlApp : Application() {
Timber.e(e, "Failed to start GateDrop")
}

// Mail: the Microsoft redirect only with the plugin; its offline copy goes with the plugin or the login.
try {
EntryPointAccessors.fromApplication(this, com.gatecontrol.android.mail.MailEntryPoint::class.java).mailStartup().start()
} catch (e: Throwable) {
Timber.w(e, "Mail: not started")
}

// Google Play build: in-app update (network watch, install in the background).
if (com.gatecontrol.android.update.PlayUpdater.enabled) {
try {
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/java/com/gatecontrol/android/MailSigninActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.gatecontrol.android

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.gatecontrol.android.mail.MailMicrosoft
import com.gatecontrol.android.mail.MailSigninRedirects

/**
* Receives `msauth://com.gatecontrol.client/<hash>?code=…`, the browser's answer to "Mit Microsoft anmelden" of a
* mail account, and hands it to the running Mail screen ([MailSigninRedirects]), bringing up [MainActivity] again
* (which closes the Custom Tab above it). Invisible, never shown in Recents.
*
* Exported because the browser opens it, and enabled only while the server offers Mail (MailStartup). The link
* carries a one-time code that is useless without the PKCE verifier, and the Mail screen drops any answer whose
* state it did not create.
*/
class MailSigninActivity : Activity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val raw = intent?.dataString
if (raw != null && MailMicrosoft.isRedirect(raw)) {
MailSigninRedirects.offer(raw)
startActivity(
Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP),
)
}
finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ data class PluginAvailability(
* listed for this user: the tab "Drive".
*/
val drive: Boolean = false,
/**
* The Mail plugin is listed with an app tab for this user (installed and the person allowed, its `appVisible`):
* the tab "Mail" (contract D).
*/
val mail: Boolean = false,
) {
companion object {
val NONE = PluginAvailability()
Expand All @@ -88,6 +93,7 @@ data class PluginAvailability(
gatedrop = plugins.any { it.id == com.gatecontrol.android.gatedrop.GATEDROP_PLUGIN },
vault = plugins.any { it.id == com.gatecontrol.android.vault.VAULT_PLUGIN },
drive = plugins.any { it.id == com.gatecontrol.android.drive.DRIVE_PLUGIN },
mail = plugins.any { it.id == com.gatecontrol.android.mail.MAIL_PLUGIN && it.tab != null },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,14 @@ class GateDropPushHandler @Inject constructor(
}
}

/** Realtime events of the push stream for the chats, GateDrop and Drive. */
/** Realtime events of the push stream for the chats, GateDrop, Drive and Mail. */
@Singleton
class AppPluginEventSink @Inject constructor(
private val chats: com.gatecontrol.android.chat.ChatEventSink,
private val gatedrop: dagger.Lazy<GateDropPushHandler>,
private val gatedropRepository: dagger.Lazy<GateDropRepository>,
private val drive: dagger.Lazy<com.gatecontrol.android.drive.DriveLive>,
private val mail: dagger.Lazy<com.gatecontrol.android.mail.MailRepository>,
) : PluginEventSink {
override fun onPluginEvent(plugin: String, type: String, data: JsonElement?) {
if (plugin == GATEDROP_PLUGIN) {
Expand All @@ -146,6 +147,10 @@ class AppPluginEventSink @Inject constructor(
runCatching { drive.get().onPluginEvent(type, data) }
return
}
if (plugin == com.gatecontrol.android.mail.MAIL_PLUGIN) {
runCatching { mail.get().onPluginEvent(type, data) }
return
}
chats.onPluginEvent(plugin, type, data)
}

Expand All @@ -155,6 +160,8 @@ class AppPluginEventSink @Inject constructor(
runCatching { gatedropRepository.get().pollPendingSoon() }
// Drive's changes of that time: whatever is open loads again.
runCatching { drive.get().onStreamOpen() }
// Mail: the lists load again when the tab is open
runCatching { mail.get().onStreamOpen() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.gatecontrol.android.layout

/**
* Pages beside the start tab (pure, unit-tested): functions that are a tab
* otherwise (Chats, Vault, Drive, Netzwerk, Familie) pinned to Start, and
* otherwise (Chats, Mail, Vault, Drive, Netzwerk, Familie) pinned to Start, and
* widget pages ("Widgets 2", "Widgets 3" …) holding quick actions and
* widgets like Start does.
*
Expand All @@ -22,13 +22,14 @@ object StartPages {
const val MARKER = "page:"

const val CHATS = "chats"
const val MAIL = "mail"
const val VAULT = "vault"
const val DRIVE = "drive"
const val NETWORK = "network"
const val FAMILIE = "familie"

/** Functions that can be pages, in the order of the tab bar. */
val FEATURES = listOf(CHATS, VAULT, DRIVE, NETWORK, FAMILIE)
val FEATURES = listOf(CHATS, MAIL, VAULT, DRIVE, NETWORK, FAMILIE)

private val WIDGETS = Regex("^w([0-9]{1,3})$")

Expand Down
Loading
Loading