diff --git a/CHANGES.rst b/CHANGES.rst index 507945b056..ba4754243d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ MatrixSdk: Features: - Make Widget/Integration manager optional (#3224) + - Handle terms and condition approval for IntegrationManager (#3225) Improvements: - diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 0f7d6618f8..d2a79bf0f8 100755 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -433,6 +433,10 @@ android:name=".activity.SASVerificationActivity" android:label="@string/title_activity_verify_device" android:windowSoftInputMode="stateHidden|adjustResize" /> + getString(R.string.terms_description_for_identity_server) + TermsManager.ServiceType.IntegrationManager -> getString(R.string.terms_description_for_integration_manager) + } + + termsController = TermsController(description, this) + termsList.setController(termsController) + + viewModel.loadTerms(getString(R.string.resources_language)) + + viewModel.termsList.observe(this, Observer { terms -> + when (terms) { + is MxAsync.Loading -> { + bottomBar.isVisible = false + progressBar.isVisible = true + } + is MxAsync.Error -> { + progressBar.isVisible = false + terms.stringResId.let { stringRes -> + AlertDialog.Builder(requireActivity()) + .setMessage(stringRes) + .setPositiveButton(R.string.ok) { dialog, which -> + activity?.finish() + } + .show() + } + } + is MxAsync.Success -> { + updateState(terms.value) + progressBar.isVisible = false + bottomBar.isVisible = true + acceptButton.isEnabled = terms.value.all { it.accepted } + } + } + }) + + viewModel.acceptTerms.observe(this, Observer { request -> + when (request) { + is MxAsync.Loading -> { + progressBar.isVisible = true + } + is MxAsync.Error -> { + progressBar.isVisible = false + request.stringResId.let { stringRes -> + AlertDialog.Builder(requireActivity()) + .setMessage(stringRes) + .setPositiveButton(R.string.ok, null) + .show() + } + } + is MxAsync.Success -> { + activity?.setResult(Activity.RESULT_OK) + activity?.finish() + } + } + }) + } + + private fun updateState(terms: List) { + termsController.setData(terms) + } + + companion object { + fun newInstance(): AcceptTermsFragment { + return AcceptTermsFragment() + } + } + + + override fun setChecked(term: Term, isChecked: Boolean) { + viewModel.markTermAsAccepted(term.url, isChecked) + } + + override fun review(term: Term) { + openUrlInExternalBrowser(this.requireContext(), term.url) + } + + @OnClick(R.id.terms_bottom_accept) + fun onAcceptButton() { + viewModel.acceptTerms() + } + + + @OnClick(R.id.terms_bottom_decline) + fun onDeclineButton() { + activity?.finish() + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/terms/AcceptTermsViewModel.kt b/vector/src/main/java/im/vector/fragments/terms/AcceptTermsViewModel.kt new file mode 100644 index 0000000000..bd92234a44 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/terms/AcceptTermsViewModel.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.fragments.terms + +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.ViewModel +import im.vector.R +import im.vector.util.state.MxAsync +import org.matrix.androidsdk.MXSession +import org.matrix.androidsdk.core.Log +import org.matrix.androidsdk.core.callback.ApiCallback +import org.matrix.androidsdk.core.model.MatrixError +import org.matrix.androidsdk.features.terms.GetTermsResponse +import org.matrix.androidsdk.features.terms.TermsManager + +class AcceptTermsViewModel : ViewModel() { + + lateinit var termsArgs: ServiceTermsArgs + + val termsList: MutableLiveData>> = MutableLiveData() + + val acceptTerms: MutableLiveData> = MutableLiveData() + + var mxSession: MXSession? = null + var termsManager: TermsManager? = null + + fun markTermAsAccepted(url: String, accepted: Boolean) { + termsList.value?.invoke()?.map { + if (it.url == url) { + it.copy(accepted = accepted) + } else it + }?.let { + termsList.postValue(MxAsync.Success(it)) + } + } + + fun initSession(session: MXSession?) { + mxSession = session + termsManager = mxSession?.termsManager + } + + fun acceptTerms() { + val acceptedTerms = termsList.value?.invoke() ?: return + + acceptTerms.postValue(MxAsync.Loading()) + val agreedUrls = acceptedTerms.map { it.url } + + termsManager?.agreeToTerms(termsArgs.type, + termsArgs.baseURL, + agreedUrls, + termsArgs.token, + object : ApiCallback { + override fun onSuccess(info: Unit) { + acceptTerms.postValue(MxAsync.Success(Unit)) + } + + override fun onUnexpectedError(e: java.lang.Exception?) { + acceptTerms.postValue(MxAsync.Error(R.string.unknown_error)) + Log.e(LOG_TAG, "Failed to agree to terms ", e) + } + + override fun onNetworkError(e: java.lang.Exception?) { + acceptTerms.postValue(MxAsync.Error(R.string.unknown_error)) + Log.e(LOG_TAG, "Failed to agree to terms ", e) + } + + override fun onMatrixError(e: MatrixError?) { + acceptTerms.postValue(MxAsync.Error(R.string.unknown_error)) + Log.e(LOG_TAG, "Failed to agree to terms " + e?.message) + } + } + ) + } + + fun loadTerms(preferredLanguageCode: String) { + termsList.postValue(MxAsync.Loading()) + + termsManager?.get(termsArgs.type, termsArgs.baseURL, object : ApiCallback { + override fun onSuccess(info: GetTermsResponse) { + + val terms = mutableListOf() + info.serverResponse.getLocalizedPrivacyPolicies(preferredLanguageCode)?.let { + terms.add( + Term(it.localizedUrl ?: "", + it.localizedName ?: "", + it.version, + accepted = info.alreadyAcceptedTermUrls.contains(it.localizedUrl) + ) + ) + } + info.serverResponse.getLocalizedTermOfServices(preferredLanguageCode)?.let { + terms.add( + Term(it.localizedUrl ?: "", + it.localizedName ?: "", + it.version, + accepted = info.alreadyAcceptedTermUrls.contains(it.localizedUrl) + ) + ) + } + + termsList.postValue(MxAsync.Success(terms)) + } + + + override fun onUnexpectedError(e: Exception?) { + termsList.postValue(MxAsync.Error(R.string.unknown_error)) + } + + override fun onNetworkError(e: Exception?) { + termsList.postValue(MxAsync.Error(R.string.unknown_error)) + } + + override fun onMatrixError(e: MatrixError?) { + termsList.postValue(MxAsync.Error(R.string.unknown_error)) + } + + }) + } + + companion object { + private val LOG_TAG = AcceptTermsViewModel::javaClass.name + } + +} + +data class Term( + val url: String, + val name: String, + val version: String? = null, + val accepted: Boolean = false +) \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/terms/ServiceTermsArgs.kt b/vector/src/main/java/im/vector/fragments/terms/ServiceTermsArgs.kt new file mode 100644 index 0000000000..3a1ce7c5f8 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/terms/ServiceTermsArgs.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.fragments.terms + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import org.matrix.androidsdk.features.terms.TermsManager + +@Parcelize +data class ServiceTermsArgs( + val type: TermsManager.ServiceType, + val baseURL: String, + val token: String? +) : Parcelable diff --git a/vector/src/main/java/im/vector/fragments/terms/TermsController.kt b/vector/src/main/java/im/vector/fragments/terms/TermsController.kt new file mode 100644 index 0000000000..043a6ff3d5 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/terms/TermsController.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.fragments.terms + +import android.view.View +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.R +import im.vector.ui.epoxy.genericItemHeader + +class TermsController(private val itemDescription: String, + private val listener: Listener) : TypedEpoxyController>() { + + override fun buildModels(data: List?) { + data?.let { + genericItemHeader { + id("header") + textID(R.string.widget_integration_review_terms) + } + it.forEach { term -> + terms { + id(term.url) + name(term.name) + description(itemDescription) + checked(term.accepted) + + clickListener(View.OnClickListener { listener.review(term) }) + checkChangeListener { _, isChecked -> + listener.setChecked(term, isChecked) + } + } + } + } + //TODO error mgmt + } + + interface Listener { + fun setChecked(term: Term, isChecked: Boolean) + fun review(term: Term) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/terms/TermsModel.kt b/vector/src/main/java/im/vector/fragments/terms/TermsModel.kt new file mode 100644 index 0000000000..b03c6278c4 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/terms/TermsModel.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.fragments.terms + +import android.view.View +import android.widget.CheckBox +import android.widget.CompoundButton +import android.widget.TextView +import butterknife.BindView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder +import im.vector.R +import im.vector.ui.epoxy.BaseEpoxyHolder + +@EpoxyModelClass(layout = R.layout.item_tos) +abstract class TermsModel : EpoxyModelWithHolder() { + + @EpoxyAttribute + var checked: Boolean = false + + @EpoxyAttribute + var name: String? = null + + @EpoxyAttribute + var description: String? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: View.OnClickListener? = null + + override fun bind(holder: Holder) { + holder.checkbox.isChecked = checked + holder.title.text = name + holder.description.text = description + holder.checkbox.setOnCheckedChangeListener(checkChangeListener) + holder.main.setOnClickListener(clickListener) + } + + class Holder : BaseEpoxyHolder() { + @BindView(R.id.term_accept_checkbox) + lateinit var checkbox: CheckBox + + @BindView(R.id.term_name) + lateinit var title: TextView + + @BindView(R.id.term_description) + lateinit var description: TextView + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/ui/epoxy/GenericHeaderItem.kt b/vector/src/main/java/im/vector/ui/epoxy/GenericHeaderItem.kt new file mode 100644 index 0000000000..f1c7664a14 --- /dev/null +++ b/vector/src/main/java/im/vector/ui/epoxy/GenericHeaderItem.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.ui.epoxy + +import android.support.annotation.StringRes +import android.widget.TextView +import butterknife.BindView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder +import im.vector.R + +/** + * A generic list item header left aligned with notice color. + */ +@EpoxyModelClass(layout = R.layout.item_generic_header) +abstract class GenericItemHeader : EpoxyModelWithHolder() { + + @EpoxyAttribute + var text: String? = null + + @EpoxyAttribute + @StringRes + var textID: Int? = null + + @EpoxyAttribute + var textSizeSp: Float = 15f + + override fun bind(holder: Holder) { + if (textID != null) { + holder.textView.setText(textID!!) + } else { + holder.textView.text = text + } + holder.textView.textSize = textSizeSp + } + + class Holder : BaseEpoxyHolder() { + + @BindView(R.id.itemGenericHeaderText) + lateinit var textView: TextView + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/util/PreferencesManager.java b/vector/src/main/java/im/vector/util/PreferencesManager.java index 875205331e..bd9709ccb5 100755 --- a/vector/src/main/java/im/vector/util/PreferencesManager.java +++ b/vector/src/main/java/im/vector/util/PreferencesManager.java @@ -290,7 +290,7 @@ public static String getIntegrationManagerJitsiUrl(Context context) { } - public static void setIntegrationManagerUrls(Context context, String uiURl, String apiURl, String jitsyUrl) { + public static void setIntegrationManagerUrls(Context context, String uiURl, String apiURl, String jitsiUrl) { if (uiURl != null) { PreferenceManager.getDefaultSharedPreferences(context) .edit() @@ -304,10 +304,10 @@ public static void setIntegrationManagerUrls(Context context, String uiURl, Stri .apply(); } - if (jitsyUrl != null) { + if (jitsiUrl != null) { PreferenceManager.getDefaultSharedPreferences(context) .edit() - .putString(SETTINGS_INTEGRATION_MANAGER_JITSI_URL, jitsyUrl) + .putString(SETTINGS_INTEGRATION_MANAGER_JITSI_URL, jitsiUrl) .apply(); } } diff --git a/vector/src/main/java/im/vector/util/state/MxAsync.kt b/vector/src/main/java/im/vector/util/state/MxAsync.kt new file mode 100644 index 0000000000..df7df24e46 --- /dev/null +++ b/vector/src/main/java/im/vector/util/state/MxAsync.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.util.state + +import android.support.annotation.StringRes + +sealed class MxAsync { + + open operator fun invoke(): T? = null + + class Loading : MxAsync() + + data class Error(@StringRes val stringResId: Int) : MxAsync() + + data class Success(val value: T) : MxAsync() { + override operator fun invoke(): T = value + } + +} diff --git a/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt b/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt index 8ac9790702..d38aeba552 100644 --- a/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt +++ b/vector/src/main/java/im/vector/widgets/IntegrationManagerConfig.kt @@ -18,7 +18,7 @@ package im.vector.widgets /** * Configuration for an integration manager. - * By default, it uses URLs defined in the app settings but they can be overidden. + * By default, it uses URLs defined in the app settings but they can be overridden. */ data class IntegrationManagerConfig( val uiUrl: String, diff --git a/vector/src/main/java/im/vector/widgets/WidgetsApi.java b/vector/src/main/java/im/vector/widgets/WidgetsApi.java index 8108210ced..9658bbabcc 100644 --- a/vector/src/main/java/im/vector/widgets/WidgetsApi.java +++ b/vector/src/main/java/im/vector/widgets/WidgetsApi.java @@ -19,6 +19,7 @@ import retrofit2.Call; import retrofit2.http.Body; +import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; @@ -30,4 +31,8 @@ interface WidgetsApi { */ @POST("register") Call> register(@Body Map body, @Query("v") String version); + + @GET("account") + Call> validateToken( @Query("scalar_token") String scalarToken, @Query("v") String version); + } diff --git a/vector/src/main/java/im/vector/widgets/WidgetsManager.java b/vector/src/main/java/im/vector/widgets/WidgetsManager.java index 17d9fc5a73..3d66f3d2fa 100755 --- a/vector/src/main/java/im/vector/widgets/WidgetsManager.java +++ b/vector/src/main/java/im/vector/widgets/WidgetsManager.java @@ -19,7 +19,6 @@ package im.vector.widgets; import android.content.Context; -import android.support.v7.preference.PreferenceManager; import android.text.TextUtils; import com.google.gson.JsonObject; @@ -30,8 +29,10 @@ import org.matrix.androidsdk.core.callback.SimpleApiCallback; import org.matrix.androidsdk.core.model.MatrixError; import org.matrix.androidsdk.data.Room; +import org.matrix.androidsdk.features.terms.TermsNotSignedException; import org.matrix.androidsdk.rest.model.Event; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -48,6 +49,7 @@ import im.vector.VectorApp; import im.vector.extensions.UrlExtensionsKt; import im.vector.settings.VectorLocale; +import im.vector.widgets.tokens.TokensStore; public class WidgetsManager { private static final String LOG_TAG = WidgetsManager.class.getSimpleName(); @@ -67,11 +69,6 @@ public class WidgetsManager { */ private static final String WIDGET_TYPE_JITSI = "jitsi"; - /** - * Widget preferences - */ - private static final String SCALAR_TOKEN_PREFERENCE_KEY = "SCALAR_TOKEN_PREFERENCE_KEY"; - private final IntegrationManagerConfig config; public WidgetsManager(IntegrationManagerConfig config) { @@ -81,6 +78,7 @@ public WidgetsManager(IntegrationManagerConfig config) { public String getUIUrl() { return config.getUiUrl(); } + /** * Widget error code */ @@ -530,12 +528,37 @@ public boolean isScalarUrl(Context context, String url) { * @param callback the asynchronous callback */ public void getScalarToken(final Context context, final MXSession session, final ApiCallback callback) { - final String preferenceKey = SCALAR_TOKEN_PREFERENCE_KEY + session.getMyUserId(); + final TokensStore tokensStore = new TokensStore(context); - final String scalarToken = PreferenceManager.getDefaultSharedPreferences(context).getString(preferenceKey, null); + final String scalarToken = tokensStore.getToken(session.getMyUserId(), config.getApiUrl()); if (null != scalarToken) { - callback.onSuccess(scalarToken); + WidgetsRestClient widgetsRestClient = new WidgetsRestClient(context, config); + widgetsRestClient.validateToken(scalarToken, new SimpleApiCallback>(callback) { + + @Override + public void onSuccess(Map info) { + if (null != callback) { + callback.onSuccess(scalarToken); + } + } + + @Override + public void onMatrixError(MatrixError e) { + if (MatrixError.TERMS_NOT_SIGNED.equals(e.errcode)) { + if (null != callback) { + callback.onUnexpectedError(new TermsNotSignedException(scalarToken)); + } + } else if (e.mStatus == HttpURLConnection.HTTP_FORBIDDEN /* 403 */) { + // Refresh the token + Log.w(LOG_TAG, "Invalid token, clear it and get a new token"); + clearScalarToken(context, session); + getScalarToken(context, session, callback); + } else { + super.onMatrixError(e); + } + } + }); } else { session.openIdToken(new SimpleApiCallback>(callback) { @Override @@ -548,15 +571,29 @@ public void onSuccess(Map response) { String token = response.get("scalar_token"); if (null != token) { - PreferenceManager.getDefaultSharedPreferences(context) - .edit() - .putString(preferenceKey, token) - .apply(); + tokensStore.setToken(session.getMyUserId(), config.getApiUrl(), token); } - if (null != callback) { - callback.onSuccess(token); - } + // Validate it (this mostly checks to see if the IM needs us to agree to some terms) + + widgetsRestClient.validateToken(token, new SimpleApiCallback>(callback) { + @Override + public void onSuccess(Map info) { + if (null != callback) { + callback.onSuccess(token); + } + } + + @Override + public void onMatrixError(MatrixError e) { + if (MatrixError.TERMS_NOT_SIGNED.equals(e.errcode)) { + callback.onUnexpectedError(new TermsNotSignedException(token)); + } else { + super.onMatrixError(e); + } + } + }); + } }); } @@ -571,11 +608,6 @@ public void onSuccess(Map response) { * @param session current session, to retrieve the current user */ public void clearScalarToken(Context context, final MXSession session) { - final String preferenceKey = SCALAR_TOKEN_PREFERENCE_KEY + session.getMyUserId(); - - PreferenceManager.getDefaultSharedPreferences(context) - .edit() - .remove(preferenceKey) - .apply(); + new TokensStore(context).clear(); } } diff --git a/vector/src/main/java/im/vector/widgets/WidgetsRestClient.java b/vector/src/main/java/im/vector/widgets/WidgetsRestClient.java index dab54abeb3..d34abbaddf 100644 --- a/vector/src/main/java/im/vector/widgets/WidgetsRestClient.java +++ b/vector/src/main/java/im/vector/widgets/WidgetsRestClient.java @@ -55,4 +55,14 @@ public void register(final Map params, final ApiCallback(description, mUnsentEventsManager, callback, () -> register(params, callback))); } + + /** + * Validates the scalar token to the server + */ + public void validateToken(final String scalarToken, final ApiCallback> callback) { + final String description = "Validate"; + + mApi.validateToken(scalarToken, API_VERSION).enqueue(new RestAdapterCallback<>(description, + mUnsentEventsManager, callback, null)); + } } diff --git a/vector/src/main/java/im/vector/widgets/tokens/TokensStore.kt b/vector/src/main/java/im/vector/widgets/tokens/TokensStore.kt new file mode 100644 index 0000000000..9f039cabbe --- /dev/null +++ b/vector/src/main/java/im/vector/widgets/tokens/TokensStore.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.widgets.tokens + +import android.content.Context +import android.support.v7.preference.PreferenceManager +import androidx.core.content.edit +import org.matrix.androidsdk.core.JsonUtils + +class TokensStore(context: Context) { + + private val prefs = PreferenceManager.getDefaultSharedPreferences(context) + private val gson = JsonUtils.getBasicGson() + + private data class TokensStore( + // Keys are user Id + @JvmField + val userToServerTokens: MutableMap = mutableMapOf() + ) + + private data class ServerTokens( + // Keys are server Url, values are token + @JvmField + val serverTokens: MutableMap = mutableMapOf() + ) + + fun getToken(userId: String, serverUrl: String): String? { + handleMigration(userId) + + return readStore() + .userToServerTokens[userId] + ?.serverTokens + ?.get(serverUrl) + } + + private fun handleMigration(userId: String) { + val prefKey = SCALAR_TOKEN_LEGACY_PREFERENCE_KEY + userId + + val previousStoredToken = prefs.getString(prefKey, null) + + if (!previousStoredToken.isNullOrBlank()) { + // It was maybe a token for scalar.vector.im. If it is not the case, it will be invalid and will be replaced. + setToken(userId, "https://scalar.vector.im/api", previousStoredToken) + + prefs.edit { + remove(prefKey) + } + } + } + + fun setToken(userId: String, serverUrl: String, token: String) { + readStore() + .apply { + userToServerTokens.getOrPut(userId) { ServerTokens() } + .serverTokens[serverUrl] = token + } + .commit() + } + + private fun readStore(): TokensStore { + return prefs.getString(SCALAR_TOKENS_PREFERENCE_KEY, null) + ?.toModel() + ?: TokensStore() + } + + private fun TokensStore.commit() { + prefs.edit { + putString(SCALAR_TOKENS_PREFERENCE_KEY, this@commit.fromModel()) + } + } + + fun clear() { + prefs.edit { + remove(SCALAR_TOKENS_PREFERENCE_KEY) + } + } + + private fun String.toModel(): TokensStore? { + return gson.fromJson(this, TokensStore::class.java) + } + + private fun TokensStore.fromModel(): String? { + return gson.toJson(this) + } + + companion object { + private const val SCALAR_TOKEN_LEGACY_PREFERENCE_KEY = "SCALAR_TOKEN_PREFERENCE_KEY" + + private const val SCALAR_TOKENS_PREFERENCE_KEY = "SCALAR_TOKENS_PREFERENCE_KEY" + } +} \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_accept_terms.xml b/vector/src/main/res/layout/fragment_accept_terms.xml new file mode 100644 index 0000000000..2ab38531dc --- /dev/null +++ b/vector/src/main/res/layout/fragment_accept_terms.xml @@ -0,0 +1,58 @@ + + + + + + + + +