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
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public class MainActivityViewModel implements ActivityViewModel, ISubscriptionCh
private RelativeLayout appIdRelativeLayout;
private TextView appIdTitleTextView;
private TextView appIdTextView;
private Button switchUserButton;
private Button loginUserButton;
private Button logoutUserButton;

// Alias
private TextView aliasTitleTextView;
Expand Down Expand Up @@ -213,7 +214,8 @@ public ActivityViewModel onActivityCreated(Context context) {
appIdTitleTextView = getActivity().findViewById(R.id.main_activity_account_details_app_id_title_text_view);
appIdTextView = getActivity().findViewById(R.id.main_activity_account_details_app_id_text_view);
revokeConsentButton = getActivity().findViewById(R.id.main_activity_app_revoke_consent_button);
switchUserButton = getActivity().findViewById(R.id.main_activity_switch_user_button);
loginUserButton = getActivity().findViewById(R.id.main_activity_login_user_button);
logoutUserButton = getActivity().findViewById(R.id.main_activity_logout_user_button);

aliasTitleTextView = getActivity().findViewById(R.id.main_activity_aliases_title_text_view);
noAliasesTextView = getActivity().findViewById(R.id.main_activity_aliases_no_aliases_text_view);
Expand Down Expand Up @@ -301,7 +303,8 @@ public ActivityViewModel setupInterfaceElements() {
font.applyFont(privacyConsentAllowButton, font.saralaBold);
font.applyFont(appIdTitleTextView, font.saralaBold);
font.applyFont(appIdTextView, font.saralaRegular);
font.applyFont(switchUserButton, font.saralaBold);
font.applyFont(loginUserButton, font.saralaBold);
font.applyFont(logoutUserButton, font.saralaBold);
font.applyFont(aliasTitleTextView, font.saralaBold);
font.applyFont(noAliasesTextView, font.saralaBold);
font.applyFont(emailHeaderTextView, font.saralaBold);
Expand Down Expand Up @@ -401,37 +404,28 @@ public void onScrollChanged() {
private void setupAppLayout() {
revokeConsentButton.setOnClickListener(v -> togglePrivacyConsent(false));

if(SharedPreferenceUtil.getCachedIsLoggedIn(context)) {
switchUserButton.setText(R.string.logout_user);
}

switchUserButton.setOnClickListener(v -> {
if(SharedPreferenceUtil.getCachedIsLoggedIn(context)) {
OneSignal.logout(Continue.with(r -> {
SharedPreferenceUtil.cacheIsLoggedIn(context, false);
switchUserButton.setText(R.string.login_user);
refreshState();
}));
}
else {
dialog.createUpdateAlertDialog("", Dialog.DialogAction.LOGIN, ProfileUtil.FieldType.EXTERNAL_USER_ID, new UpdateAlertDialogCallback() {
@Override
public void onSuccess(String update) {
if (update != null && !update.isEmpty()) {
OneSignal.login(update, Continue.with(r -> {
SharedPreferenceUtil.cacheIsLoggedIn(context, true);
switchUserButton.setText(R.string.logout_user);
refreshState();
}));
}
loginUserButton.setOnClickListener(v -> {
dialog.createUpdateAlertDialog("", Dialog.DialogAction.LOGIN, ProfileUtil.FieldType.EXTERNAL_USER_ID, new UpdateAlertDialogCallback() {
@Override
public void onSuccess(String update) {
if (update != null && !update.isEmpty()) {
OneSignal.login(update, Continue.with(r -> {
refreshState();
}));
}
}

@Override
public void onFailure() {
@Override
public void onFailure() {

}
});
}
}
});
});

logoutUserButton.setOnClickListener(v -> {
OneSignal.logout(Continue.with(r -> {
refreshState();
}));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public class SharedPreferenceUtil {
public static final String USER_EXTERNAL_USER_ID_SHARED_PREF = "USER_EXTERNAL_USER_ID_SHARED_PREF";
private static final String LOCATION_SHARED_PREF = "LOCATION_SHARED_PREF";
private static final String IN_APP_MESSAGING_PAUSED_PREF = "IN_APP_MESSAGING_PAUSED_PREF";
private static final String IS_LOGGED_IN = "IS_LOGGED_IN";

private static SharedPreferences getSharedPreference(Context context) {
return context.getSharedPreferences(APP_SHARED_PREFS, Context.MODE_PRIVATE);
Expand Down Expand Up @@ -44,10 +43,6 @@ public static boolean getCachedInAppMessagingPausedStatus(Context context) {
return getSharedPreference(context).getBoolean(IN_APP_MESSAGING_PAUSED_PREF, true);
}

public static boolean getCachedIsLoggedIn(Context context) {
return getSharedPreference(context).getBoolean(IS_LOGGED_IN, false);
}

public static void cacheOneSignalAppId(Context context, String appId) {
getSharedPreference(context).edit().putString(OS_APP_ID_SHARED_PREF, appId).apply();
}
Expand All @@ -67,8 +62,4 @@ public static void cacheLocationSharedStatus(Context context, boolean subscribed
public static void cacheInAppMessagingPausedStatus(Context context, boolean paused) {
getSharedPreference(context).edit().putBoolean(IN_APP_MESSAGING_PAUSED_PREF, paused).apply();
}

public static void cacheIsLoggedIn(Context context, boolean isLoggedIn) {
getSharedPreference(context).edit().putBoolean(IS_LOGGED_IN, isLoggedIn).apply();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@
android:visibility="visible"/>

</LinearLayout>

<!-- Login -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
Expand All @@ -258,7 +260,7 @@
android:orientation="vertical">

<Button
android:id="@+id/main_activity_switch_user_button"
android:id="@+id/main_activity_login_user_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/login_user"
Expand All @@ -268,6 +270,30 @@
android:visibility="visible"/>

</LinearLayout>

<!-- Logout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="center"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="@color/colorPrimary"
android:gravity="center"
android:orientation="vertical">

<Button
android:id="@+id/main_activity_logout_user_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/logout_user"
android:textSize="19sp"
android:textColor="@android:color/white"
android:background="@drawable/ripple_selector_white_red"
android:visibility="visible"/>
</LinearLayout>
</LinearLayout>

<!-- Aliases -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ class ConfigModel : Model() {
get() = getStringProperty(::appId.name)
set(value) { setStringProperty(::appId.name, value) }

/**
* This device's push subscription ID.
*/
var pushSubscriptionId: String?
get() = getOptStringProperty(::pushSubscriptionId.name)
set(value) { setOptStringProperty(::pushSubscriptionId.name, value) }

/**
* The API URL String.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,40 +62,40 @@ internal class ConfigModelStoreListener(
var success = false
do {
try {
val params = _paramsBackendService.fetchParams(
appId,
_subscriptionManager.subscriptions.push.id.ifEmpty { null }
)
val params = _paramsBackendService.fetchParams(appId, _subscriptionManager.subscriptions.push.id.ifEmpty { null })

// copy current model into new model, then override with what comes down.
val config = ConfigModel()
config.initializeFromModel(null, _configModelStore.model)

// these are always copied from the backend params
config.appId = appId
config.enterprise = params.enterprise ?: _configModelStore.model.enterprise
config.useIdentityVerification = params.useIdentityVerification ?: _configModelStore.model.useIdentityVerification
config.notificationChannels = params.notificationChannels
config.firebaseAnalytics = params.firebaseAnalytics ?: _configModelStore.model.firebaseAnalytics
config.restoreTTLFilter = params.restoreTTLFilter ?: _configModelStore.model.restoreTTLFilter
config.googleProjectNumber = params.googleProjectNumber
config.clearGroupOnSummaryClick = params.clearGroupOnSummaryClick ?: _configModelStore.model.clearGroupOnSummaryClick
config.receiveReceiptEnabled = params.receiveReceiptEnabled ?: _configModelStore.model.receiveReceiptEnabled
config.disableGMSMissingPrompt = params.disableGMSMissingPrompt ?: _configModelStore.model.disableGMSMissingPrompt
config.unsubscribeWhenNotificationsDisabled = params.unsubscribeWhenNotificationsDisabled ?: _configModelStore.model.unsubscribeWhenNotificationsDisabled
config.locationShared = params.locationShared ?: _configModelStore.model.locationShared
config.requiresPrivacyConsent = params.requiresUserPrivacyConsent ?: _configModelStore.model.requiresPrivacyConsent
config.opRepoExecutionInterval = params.opRepoExecutionInterval ?: _configModelStore.model.opRepoExecutionInterval
config.givenPrivacyConsent = _configModelStore.model.givenPrivacyConsent

config.influenceParams.notificationLimit = params.influenceParams.notificationLimit ?: _configModelStore.model.influenceParams.notificationLimit
config.influenceParams.indirectNotificationAttributionWindow = params.influenceParams.indirectNotificationAttributionWindow ?: _configModelStore.model.influenceParams.indirectNotificationAttributionWindow
config.influenceParams.iamLimit = params.influenceParams.iamLimit ?: _configModelStore.model.influenceParams.iamLimit
config.influenceParams.indirectIAMAttributionWindow = params.influenceParams.indirectIAMAttributionWindow ?: _configModelStore.model.influenceParams.indirectIAMAttributionWindow
config.influenceParams.isDirectEnabled = params.influenceParams.isDirectEnabled ?: _configModelStore.model.influenceParams.isDirectEnabled
config.influenceParams.isIndirectEnabled = params.influenceParams.isIndirectEnabled ?: _configModelStore.model.influenceParams.isIndirectEnabled
config.influenceParams.isUnattributedEnabled = params.influenceParams.isUnattributedEnabled ?: _configModelStore.model.influenceParams.isUnattributedEnabled

config.fcmParams.projectId = params.fcmParams.projectId
config.fcmParams.appId = params.fcmParams.appId
config.fcmParams.apiKey = params.fcmParams.apiKey

// these are only copied from the backend params when the backend has set them.
params.enterprise?.let { config.enterprise = it }
params.useIdentityVerification?.let { config.useIdentityVerification = it }
params.firebaseAnalytics?.let { config.firebaseAnalytics = it }
params.restoreTTLFilter?.let { config.restoreTTLFilter = it }
params.clearGroupOnSummaryClick?.let { config.clearGroupOnSummaryClick = it }
params.receiveReceiptEnabled?.let { config.receiveReceiptEnabled = it }
params.disableGMSMissingPrompt?.let { config.disableGMSMissingPrompt = it }
params.unsubscribeWhenNotificationsDisabled?.let { config.unsubscribeWhenNotificationsDisabled = it }
params.locationShared?.let { config.locationShared = it }
params.requiresUserPrivacyConsent?.let { config.requiresPrivacyConsent = it }
params.opRepoExecutionInterval?.let { config.opRepoExecutionInterval = it }
params.influenceParams.notificationLimit?.let { config.influenceParams.notificationLimit = it }
params.influenceParams.indirectNotificationAttributionWindow?.let { config.influenceParams.indirectNotificationAttributionWindow = it }
params.influenceParams.iamLimit?.let { config.influenceParams.iamLimit = it }
params.influenceParams.indirectIAMAttributionWindow?.let { config.influenceParams.indirectIAMAttributionWindow = it }
params.influenceParams.isDirectEnabled?.let { config.influenceParams.isDirectEnabled = it }
params.influenceParams.isIndirectEnabled?.let { config.influenceParams.isIndirectEnabled = it }
params.influenceParams.isUnattributedEnabled?.let { config.influenceParams.isUnattributedEnabled }

_configModelStore.replace(config, ModelChangeTags.HYDRATE)
success = true
} catch (ex: BackendException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {

// only allow one login/logout at a time
_loginMutex.withLock {
if (_identityModelStore!!.model.externalId == null) {
return
}

createAndSwitchToNewUser()
_operationRepo!!.enqueue(LoginUserOperation(_configModel!!.appId, _identityModelStore!!.model.onesignalId, _identityModelStore!!.model.externalId))
// TODO: remove JWT Token for all future requests.
Expand All @@ -291,22 +295,29 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
val subscriptions = mutableListOf<SubscriptionModel>()

// Create the push subscription for this device under the new user, copying the current
// user's push subscription if one exists.
val currentPushSubscription = _subscriptionModelStore!!.list().firstOrNull { it.type == SubscriptionType.PUSH }
// user's push subscription if one exists. We also copy the ID. If the ID is local there
// will already be a CreateSubscriptionOperation on the queue. If the ID is remote the subscription
// will be automatically transferred over to this new user being created. If there is no
// current push subscription we do a "normal" replace which will drive adding a CreateSubscriptionOperation
// to the queue.
val currentPushSubscription = _subscriptionModelStore!!.list().firstOrNull { it.id == _configModel!!.pushSubscriptionId }
val newPushSubscription = SubscriptionModel()

newPushSubscription.id = IDManager.createLocalId()
newPushSubscription.id = currentPushSubscription?.id ?: IDManager.createLocalId()
newPushSubscription.type = SubscriptionType.PUSH
newPushSubscription.optedIn = currentPushSubscription?.optedIn ?: true
newPushSubscription.address = currentPushSubscription?.address ?: ""
newPushSubscription.status = currentPushSubscription?.status ?: SubscriptionStatus.NO_PERMISSION

// ensure we always know this devices push subscription ID
_configModel!!.pushSubscriptionId = newPushSubscription.id

subscriptions.add(newPushSubscription)

// The next 4 lines makes this user the effective user locally. We clear the subscriptions
// first as an internal change because we don't want to drive deleting the cleared subscriptions
// first as a `NO_PROPOGATE` change because we don't want to drive deleting the cleared subscriptions
// on the backend. Once cleared we can then setup the new identity/properties model, and add
// the new user's subscriptions as a "normal" change, which will drive changes to the backend.
// the new user's subscriptions as a `NORMAL` change, which will drive changes to the backend.
_subscriptionModelStore!!.clear(ModelChangeTags.NO_PROPOGATE)
_identityModelStore!!.replace(identityModel)
_propertiesModelStore!!.replace(propertiesModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@ interface ISubscriptionBackendService {
* Delete an existing subscription.
*
* @param appId The ID of the OneSignal application this subscription exists under.
* @param subscriptionId The ID of the subscription to update.
* @param subscriptionId The ID of the subscription to delete.
*/
suspend fun deleteSubscription(appId: String, subscriptionId: String)

/**
* Transfer an existing subscription to the user specified.
*
* @param appId The ID of the OneSignal application this subscription exists under.
* @param subscriptionId The ID of the subscription to transfer.
* @param aliasLabel The alias label of the user to transfer the subscription under.
* @param aliasValue The identifier within the [aliasLabel] that identifies the user to transfer under.
*/
suspend fun transferSubscription(appId: String, subscriptionId: String, aliasLabel: String, aliasValue: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,15 @@ internal class SubscriptionBackendService(
throw BackendException(response.statusCode, response.payload)
}
}

override suspend fun transferSubscription(appId: String, subscriptionId: String, aliasLabel: String, aliasValue: String) {
val requestJSON = JSONObject()
.put("identity", JSONObject().put(aliasLabel, aliasValue))

val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId/owner", requestJSON)

if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload)
}
}
}
Loading