Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intent for launching a specific App In CommCare #2710

Merged
merged 7 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,5 @@
<string name="dependency_missing_dialog_playstore_not_found" cc:translatable="true">No Play Store found on your device</string>
<string name="fcm_notification">FCM Notification</string>
<string name="fcm_default_notification_channel">notification-channel-server-communications</string>
<string name="app_with_id_not_found">Required CommCare App is not installed on device</string>
</resources>
27 changes: 27 additions & 0 deletions app/src/org/commcare/activities/DispatchActivity.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.commcare.activities;

import static org.commcare.commcaresupportlibrary.CommCareLauncher.SESSION_ENDPOINT_APP_ID;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
Expand All @@ -24,6 +26,8 @@

import androidx.appcompat.app.AppCompatActivity;

import javax.annotation.Nullable;

/**
* Dispatches install, login, and home screen activities.
*
Expand Down Expand Up @@ -179,6 +183,9 @@ private void dispatch() {
}
} else if (!CommCareApplication.instance().getSession().isActive()) {
launchLoginScreen();
} else if (needAnotherAppLogin()){
CommCareApplication.instance().closeUserSession();
launchLoginScreen();
} else if (isExternalLaunch()) {
// CommCare was launched from an external app, with a session descriptor
handleExternalLaunch();
Expand All @@ -195,6 +202,17 @@ private void dispatch() {
}
}

private boolean needAnotherAppLogin() {
String sesssionEndpointAppID = getSessionEndpointAppId();
if (sesssionEndpointAppID != null) {
CommCareApp currentApp = CommCareApplication.instance().getCurrentApp();
if (currentApp != null) {
return !currentApp.getUniqueId().contentEquals(sesssionEndpointAppID);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious about the use of contentEquals() here instead of just equals(). From what I understand contentEquals() just calls equals() under the hood when the passed input is a String, so functionally there should be no difference. So this may just be a consistency nit.

But in LoginActivity line 561 I see we're only calling equals(). When I search for "getUniqueId." I see 4 times that we use equals() and one that we use contentEquals(). Okay to ignore this if you don't think it's worth changing.

}
}
return false;
}

private boolean isExternalLaunch() {
return this.getIntent().hasExtra(SESSION_REQUEST) ||
this.getIntent().hasExtra(SESSION_ENDPOINT_ID);
Expand Down Expand Up @@ -259,6 +277,10 @@ private void launchLoginScreen() {
// AMS 06/09/16: This check is needed due to what we believe is a bug in the Android platform
Intent i = new Intent(this, LoginActivity.class);
i.putExtra(LoginActivity.USER_TRIGGERED_LOGOUT, userTriggeredLogout);
String sesssionEndpointAppID = getSessionEndpointAppId();
if (sesssionEndpointAppID != null) {
i.putExtra(LoginActivity.EXTRA_APP_ID, sesssionEndpointAppID);
}
startActivityForResult(i, LOGIN_USER);
waitingForActivityResultFromLogin = true;
} else {
Expand All @@ -269,6 +291,11 @@ private void launchLoginScreen() {
}
}

@Nullable
private String getSessionEndpointAppId() {
return getIntent().getStringExtra(SESSION_ENDPOINT_APP_ID);
}

private void launchHomeScreen() {
Intent i;
if (useRootMenuHomeActivity()) {
Expand Down
11 changes: 11 additions & 0 deletions app/src/org/commcare/activities/LoginActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class LoginActivity extends CommCareActivity<LoginActivity>
implements OnItemSelectedListener, DataPullController,
RuntimePermissionRequester, WithUIController, PullTaskResultReceiver {

public static final String EXTRA_APP_ID = "extra_app_id";
private static final String TAG = LoginActivity.class.getSimpleName();

public static final int MENU_DEMO = Menu.FIRST;
Expand Down Expand Up @@ -101,6 +102,7 @@ public class LoginActivity extends CommCareActivity<LoginActivity>

private LoginActivityUIController uiController;
private FormAndDataSyncer formAndDataSyncer;
private String presetAppID;

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand All @@ -117,6 +119,8 @@ protected void onCreate(Bundle savedInstanceState) {
uiController.setupUI();
formAndDataSyncer = new FormAndDataSyncer();

presetAppID = getIntent().getStringExtra(EXTRA_APP_ID);

if (savedInstanceState == null) {
// Only restore last user on the initial creation
uiController.restoreLastUser();
Expand Down Expand Up @@ -550,7 +554,10 @@ protected void populateAppSpinner(ArrayList<ApplicationRecord> readyApps) {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// Retrieve the app record corresponding to the app selected
String appId = appIdDropdownList.get(position);
seatAppIfNeeded(appId);
}

protected void seatAppIfNeeded(String appId) {
boolean selectedNewApp = !appId.equals(CommCareApplication.instance().getCurrentApp().getUniqueId());
if (selectedNewApp) {
// Set the id of the last selected app
Expand Down Expand Up @@ -740,4 +747,8 @@ private void checkManagedConfiguration() {
initiateLoginAttempt(false);
}
}

protected String getPresetAppID() {
return presetAppID;
}
}
33 changes: 24 additions & 9 deletions app/src/org/commcare/activities/LoginActivityUIController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.StateSet;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
Expand All @@ -21,6 +17,8 @@
import android.widget.Spinner;
import android.widget.TextView;

import androidx.preference.PreferenceManager;

import org.commcare.CommCareApplication;
import org.commcare.CommCareNoficationManager;
import org.commcare.android.database.app.models.UserKeyRecord;
Expand All @@ -43,7 +41,7 @@
import java.util.ArrayList;
import java.util.Vector;

import androidx.preference.PreferenceManager;
import javax.annotation.Nullable;

/**
* Handles login activity UI
Expand Down Expand Up @@ -196,15 +194,15 @@ public void refreshView() {

// Decide whether or not to show the app selection spinner based upon # of usable apps
ArrayList<ApplicationRecord> readyApps = MultipleAppsUtil.getUsableAppRecords();

if (readyApps.size() == 1) {
ApplicationRecord presetAppRecord = getPresetAppRecord(readyApps);
if (readyApps.size() == 1 || presetAppRecord != null) {
// Set this app as the last selected app, for use in choosing what app to initialize
// on first startup
ApplicationRecord r = readyApps.get(0);
ApplicationRecord r = presetAppRecord != null ? presetAppRecord : readyApps.get(0);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
prefs.edit().putString(LoginActivity.KEY_LAST_APP, r.getUniqueId()).apply();

setSingleAppUIState();
activity.seatAppIfNeeded(r.getUniqueId());
} else {
activity.populateAppSpinner(readyApps);
}
Expand All @@ -230,6 +228,23 @@ public void refreshView() {
}
}

@Nullable
private ApplicationRecord getPresetAppRecord(ArrayList<ApplicationRecord> readyApps) {
String presetAppId = activity.getPresetAppID();
if (presetAppId != null) {
for (ApplicationRecord readyApp : readyApps) {
if (readyApp.getUniqueId().contentEquals(presetAppId)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contentEquals() again, see above comment.

return readyApp;
}
}

// if preset App id is supplied but not found show an error
String appNotFoundError = activity.getString(R.string.app_with_id_not_found);
setErrorMessageUI(appNotFoundError, false);
}
return null;
}

protected void refreshForNewApp() {
// Remove any error content from trying to log into a different app
setStyleDefault();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.commcare.CommCareTestApplication
import org.commcare.activities.DispatchActivity
import org.commcare.activities.FormEntryActivity
import org.commcare.activities.LoginActivity
import org.commcare.activities.StandardHomeActivity
import org.commcare.android.util.TestAppInstaller
import org.commcare.android.util.TestUtils
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.commcare.commcaresupportlibrary.CommCareLauncher
import org.commcare.commcaresupportlibrary.CommCareLauncher.SESSION_ENDPOINT_APP_ID
import org.commcare.util.screen.CommCareSessionException
import org.commcare.utils.SessionUnavailableException
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import java.util.ArrayList


@Config(application = CommCareTestApplication::class)
Expand All @@ -33,6 +36,39 @@ class ExternalLaunchTests {
TestUtils.processResourceTransactionIntoAppDb("/commcare-apps/case_list_lookup/restore.xml")
}

@Test
fun testLaunchWithAppId() {
// verify initial state
val indexAndCacheTestAppId = "000b3b9ecb0806331fbd1526616eb718"
assertEquals(indexAndCacheTestAppId, CommCareTestApplication.instance().currentApp.uniqueId)
assertEquals("test", CommCareTestApplication.instance().session.loggedInUser.username)

// Install another app, this will also seat this app as current app
TestAppInstaller.installApp("jr://resource/commcare-apps/archive_form_tests/profile.ccpr")
val archiveFormTestsAppId = "6a03772aedd992c9f2c9c2198a248184"
assertEquals(archiveFormTestsAppId, CommCareTestApplication.instance().currentApp.uniqueId)
assertEquals("test", CommCareTestApplication.instance().session.loggedInUser.username)

// Request launch for seated app, since user is logged in already we should end up on app home screen
launchAndVerifyCCWithAppId(archiveFormTestsAppId, StandardHomeActivity::class.java.name)

// Request launch for non seated app, this should cause user to log out and show user Login Screen
launchAndVerifyCCWithAppId(indexAndCacheTestAppId, LoginActivity::class.java.name)
assertThrows(SessionUnavailableException::class.java) {
CommCareTestApplication.instance().session.loggedInUser
}
}

private fun launchAndVerifyCCWithAppId(appId: String, nextScreen: String) {
val intent = Intent("org.commcare.dalvik.action.CommCareSession")
intent.putExtra(SESSION_ENDPOINT_APP_ID, appId)
var dispathActivity =
Robolectric.buildActivity(DispatchActivity::class.java, intent).create().resume().get()
var recordedNextIntent = Shadows.shadowOf(dispathActivity).nextStartedActivity
var intentActivityName: String = recordedNextIntent.component!!.className
assertEquals(nextScreen, intentActivityName)
}

@Test
fun testLaunchUsingSessionEndpoint() {
// endpoint without arguments
Expand Down Expand Up @@ -67,7 +103,7 @@ class ExternalLaunchTests {

private fun verifyNextIntent(nextIntent: Intent, header: String) {
val intentActivityName: String = nextIntent.component!!.className
Assert.assertEquals(FormEntryActivity::class.java.name, intentActivityName)
assertEquals(FormEntryActivity::class.java.name, intentActivityName)
assertEquals(nextIntent.extras!!.getString(FormEntryActivity.KEY_HEADER_STRING), header)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,15 @@ private void installApp() {
new ApplicationRecord(PropertyUtils.genUUID().replace("-", ""),
ApplicationRecord.STATUS_UNINITIALIZED);


CommCareApp app = new CommCareTestApp(new CommCareApp(newRecord));

ResourceEngineTask<Object> task =
new ResourceEngineTask<Object>(app, -1, false, false) {
@Override
protected void deliverResult(Object receiver,
AppInstallStatus result) {
app.setMMResourcesValidated();
}

@Override
Expand Down
6 changes: 6 additions & 0 deletions commcare-support-library/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ Then retrieve the XML for a specific fixture from this list:

`FixtureUtils.getFixtureXml(Context context, String fixtureId`

#### CommCare Launch Helpers

Launch CommCare with a specific CommCare App:

`CommCareLauncher.launchCommCareForAppId(Context context, String appId)`

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.commcare.commcaresupportlibrary;

import android.content.Context;
import android.content.Intent;

/**
* Utility class with methods to launch CommCare
*/
public class CommCareLauncher {
public static final String SESSION_ENDPOINT_APP_ID = "ccodk_session_endpoint_app_id";
private static final String CC_LAUNCH_ACTION = "org.commcare.dalvik.action.CommCareSession";

/**
*
* @param context Android context to launch the CommCare with
* @param appId Unique Id for CommCare App that CommCare should launch with
*/
public static void launchCommCareForAppId(Context context, String appId) {
Intent intent = new Intent(CC_LAUNCH_ACTION);
intent.putExtra(SESSION_ENDPOINT_APP_ID, appId);
context.startActivity(intent);
}
}