Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
898d2e0
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
1d3867a
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
9ba972a
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
73503bd
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
da47af5
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
403e908
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
507f6d7
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
821ad82
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts May 29, 2026
66dda6c
Merge remote-tracking branch 'origin/CCCT-2437-headless-login-engine'…
conroy-ricketts Jun 4, 2026
d165331
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts Jun 4, 2026
94d61c1
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts Jun 4, 2026
3f678f7
Merge branch 'CCCT-2437-headless-login-engine' of github.com:dimagi/c…
conroy-ricketts Jun 8, 2026
d8c695b
CCCT-2437 Headless Login Engine
conroy-ricketts Jun 8, 2026
10285b8
Merge branch 'master' of github.com:dimagi/commcare-android into CCCT…
conroy-ricketts Jun 8, 2026
48e226b
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts Jun 8, 2026
3f67b5a
Merge branch 'master' of github.com:dimagi/commcare-android into CCCT…
conroy-ricketts Jun 10, 2026
6fd3a51
Merge branch 'master' of github.com:dimagi/commcare-android into CCCT…
conroy-ricketts Jun 11, 2026
128a2c9
Merge branch 'master' of github.com:dimagi/commcare-android into CCCT…
conroy-ricketts Jun 11, 2026
d6eed61
CCCT-2438 Login Routing Extraction And Inline App Seating
conroy-ricketts Jun 11, 2026
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
66 changes: 48 additions & 18 deletions app/src/org/commcare/activities/DispatchActivity.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
package org.commcare.activities;

import static org.commcare.activities.LoginActivity.EXTRA_APP_ID;
import static org.commcare.activities.LoginActivity.EXTRA_FORCE_SINGLE_APP_MODE;
import static org.commcare.commcaresupportlibrary.CommCareLauncher.SESSION_ENDPOINT_APP_ID;
import static org.commcare.connect.ConnectAppUtils.IS_LAUNCH_FROM_CONNECT;
import static org.commcare.connect.ConnectConstants.CONNECT_MANAGED_LOGIN;
import static org.commcare.connect.ConnectConstants.NOTIFICATION_ID;
import static org.commcare.connect.ConnectConstants.PERSONALID_MANAGED_LOGIN;
import static org.commcare.utils.FirebaseMessagingUtil.getNotificationActionFromIntent;

import android.content.Intent;
import android.os.Bundle;

import android.util.Log;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import org.commcare.AppUtils;
import org.commcare.CommCareApp;
import org.commcare.CommCareApplication;
Expand All @@ -26,6 +18,11 @@
import org.commcare.dalvik.R;
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.login.LaunchContext;
import org.commcare.login.LoginResult;
import org.commcare.login.PostLoginDestination;
import org.commcare.login.PostLoginOutcome;
import org.commcare.login.PostLoginRouter;
import org.commcare.preferences.DeveloperPreferences;
import org.commcare.recovery.measures.ExecuteRecoveryMeasuresActivity;
import org.commcare.recovery.measures.RecoveryMeasuresHelper;
Expand All @@ -38,10 +35,17 @@

import java.util.ArrayList;

import androidx.appcompat.app.AppCompatActivity;

import javax.annotation.Nullable;

import static org.commcare.activities.LoginActivity.EXTRA_APP_ID;
import static org.commcare.activities.LoginActivity.EXTRA_FORCE_SINGLE_APP_MODE;
import static org.commcare.commcaresupportlibrary.CommCareLauncher.SESSION_ENDPOINT_APP_ID;
import static org.commcare.connect.ConnectAppUtils.IS_LAUNCH_FROM_CONNECT;
import static org.commcare.connect.ConnectConstants.CONNECT_MANAGED_LOGIN;
import static org.commcare.connect.ConnectConstants.NOTIFICATION_ID;
import static org.commcare.connect.ConnectConstants.PERSONALID_MANAGED_LOGIN;
import static org.commcare.utils.FirebaseMessagingUtil.getNotificationActionFromIntent;

/**
* Dispatches install, login, and home screen activities.
*
Expand Down Expand Up @@ -515,12 +519,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent)
if (resultCode == RESULT_CANCELED) {
shouldFinish = true;
} else if (intent != null) {
lastLoginMode = (LoginMode)intent.getSerializableExtra(LoginActivity.LOGIN_MODE);
userManuallyEnteredPasswordMode =
intent.getBooleanExtra(LoginActivity.MANUAL_SWITCH_TO_PW_MODE, false);
personalIdManagedLogin = intent.getBooleanExtra(PERSONALID_MANAGED_LOGIN, false);
connectManagedLogin = intent.getBooleanExtra(CONNECT_MANAGED_LOGIN, false);
startFromLogin = true;
applyPostLoginDestination(routeLoginResult(intent));
}
return;
case HOME_SCREEN:
Expand All @@ -540,4 +539,35 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent)
}
super.onActivityResult(requestCode, resultCode, intent);
}

private PostLoginDestination routeLoginResult(Intent intent) {
LoginMode loginMode = (LoginMode) intent.getSerializableExtra(LoginActivity.LOGIN_MODE);
boolean manualSwitchToPwMode =
intent.getBooleanExtra(LoginActivity.MANUAL_SWITCH_TO_PW_MODE, false);
boolean personalIdManaged = intent.getBooleanExtra(PERSONALID_MANAGED_LOGIN, false);
connectManagedLogin = intent.getBooleanExtra(CONNECT_MANAGED_LOGIN, false);
boolean restoreSession = false;
LoginResult.Success success = new LoginResult.Success(
"",
"",
loginMode,
restoreSession,
personalIdManaged,
"",
new PostLoginOutcome(redirectToConnectOpportunityInfo, false)
);

return PostLoginRouter.route(success, new LaunchContext(true, manualSwitchToPwMode));
}

private void applyPostLoginDestination(PostLoginDestination destination) {
Comment thread
shubham1g5 marked this conversation as resolved.
Outdated
if (destination instanceof PostLoginDestination.Home home) {
lastLoginMode = home.getLoginMode();
userManuallyEnteredPasswordMode = home.getManualSwitchToPwMode();
personalIdManagedLogin = home.getPersonalIdManagedLogin();
startFromLogin = home.getStartFromLogin();
} else {
shouldFinish = true;
}
}
}
78 changes: 7 additions & 71 deletions app/src/org/commcare/activities/SeatAppActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

import org.commcare.CommCareApp;
import org.commcare.CommCareApplication;
import org.commcare.dalvik.R;
import org.commcare.android.database.global.models.ApplicationRecord;
import org.commcare.utils.MultipleAppsUtil;
import org.commcare.login.AppSeater;
import org.commcare.login.SeatResult;
import org.javarosa.core.services.locale.Localization;

/**
Expand All @@ -20,86 +16,26 @@
*/
public class SeatAppActivity extends CommonBaseActivity {

private static final String KEY_IN_PROGRESS = "initialization_in_progress";
public final static String KEY_APP_TO_SEAT = "app_to_seat";

private boolean inProgress;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.screen_seat_app);
TextView tv = findViewById(R.id.text);
tv.setText(Localization.get("seating.app"));

inProgress = savedInstanceState != null &&
savedInstanceState.getBoolean(KEY_IN_PROGRESS, false);

if (!inProgress) {

String idOfAppToSeat = getIntent().getStringExtra(KEY_APP_TO_SEAT);
ApplicationRecord record = MultipleAppsUtil.getAppById(idOfAppToSeat);

if (record == null) {
// No record was found for the given id
Intent i = new Intent(getIntent());
setResult(RESULT_CANCELED, i);
finish();
}

ThreadHandler handler = new ThreadHandler(this);
Thread t = new Thread(new SeatAppProcess(record, handler));
setInProgress(true);
t.start();
}
String appId = getIntent().getStringExtra(KEY_APP_TO_SEAT);
new AppSeater().start(this, appId, progress -> {}, this::finishWithResult);
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_IN_PROGRESS, inProgress);
private void finishWithResult(SeatResult result) {
setResult(RESULT_OK, new Intent(getIntent()));
Comment thread
OrangeAndGreen marked this conversation as resolved.
finish();
}

@Override
public void onBackPressed() {
// Make it impossible to quit in the middle of this activity
}

private void setInProgress(boolean b) {
Comment thread
shubham1g5 marked this conversation as resolved.
this.inProgress = b;
}

private static class ThreadHandler extends Handler {

private final SeatAppActivity activity;

public ThreadHandler(SeatAppActivity a) {
this.activity = a;
}

@Override
public void handleMessage(Message msg) {
activity.setInProgress(false);
Intent i = new Intent(activity.getIntent());
activity.setResult(RESULT_OK, i);
activity.finish();
}
}

private static class SeatAppProcess implements Runnable {

private final ApplicationRecord record;
private final ThreadHandler handler;

public SeatAppProcess(ApplicationRecord record, ThreadHandler handler) {
this.record = record;
this.handler = handler;
}

@Override
public void run() {
CommCareApplication.instance().initializeAppResources(new CommCareApp(this.record));
handler.sendEmptyMessage(0);
}
}
}
67 changes: 67 additions & 0 deletions app/src/org/commcare/login/AppSeater.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.commcare.login

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.commcare.CommCareApp
import org.commcare.CommCareApplication
import org.commcare.android.database.global.models.ApplicationRecord
import org.commcare.utils.MultipleAppsUtil

sealed class SeatResult {
object Success : SeatResult()

data class Failed(
val reason: SeatFailure,
) : SeatResult()
}

enum class SeatFailure {
APP_NOT_FOUND,
CORRUPTED,
}

class AppSeater
@JvmOverloads
constructor(
private val recordLookup: (String) -> ApplicationRecord? = { MultipleAppsUtil.getAppById(it) },
private val seatApp: (ApplicationRecord) -> Int = { record ->
Comment thread
shubham1g5 marked this conversation as resolved.
val app = CommCareApp(record)
CommCareApplication.instance().initializeAppResources(app)
app.appResourceState
},
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) {
fun interface SeatResultCallback {
fun onResult(result: SeatResult)
}

fun start(
lifecycleOwner: LifecycleOwner,
appId: String,
sink: LoginProgressSink,
callback: SeatResultCallback,
): Job =
lifecycleOwner.lifecycleScope.launch {
callback.onResult(seatIfNeeded(appId, sink))
}

suspend fun seatIfNeeded(
appId: String,
sink: LoginProgressSink,
): SeatResult {
sink.onProgress(LoginProgress(LoginPhase.Seating))
val record = recordLookup(appId) ?: return SeatResult.Failed(SeatFailure.APP_NOT_FOUND)
val resourceState = withContext(ioDispatcher) { seatApp(record) }

return if (resourceState == CommCareApplication.STATE_CORRUPTED) {
SeatResult.Failed(SeatFailure.CORRUPTED)
} else {
SeatResult.Success
}
}
}
16 changes: 16 additions & 0 deletions app/src/org/commcare/login/PostLoginDestination.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.commcare.login

import org.commcare.activities.LoginMode

sealed class PostLoginDestination {
data class Home(
val loginMode: LoginMode,
val startFromLogin: Boolean,
val manualSwitchToPwMode: Boolean,
val personalIdManagedLogin: Boolean,
) : PostLoginDestination()

data class TerminalFailure(
val error: LoginError,
) : PostLoginDestination()
}
26 changes: 26 additions & 0 deletions app/src/org/commcare/login/PostLoginRouter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.commcare.login

data class LaunchContext(
val startFromLogin: Boolean,
val manualSwitchToPwMode: Boolean,
)

object PostLoginRouter {
@JvmStatic
fun route(
result: LoginResult,
launchContext: LaunchContext,
): PostLoginDestination =
when (result) {
is LoginResult.Success ->
PostLoginDestination.Home(
loginMode = result.loginMode,
startFromLogin = launchContext.startFromLogin,
manualSwitchToPwMode = launchContext.manualSwitchToPwMode,
personalIdManagedLogin = result.personalIdManagedLogin,
)

is LoginResult.Failed ->
PostLoginDestination.TerminalFailure(result.error)
}
}
Loading
Loading