/* * Created by Itzik Braun on 12/3/2015. * Copyright (c) 2015 deluge. All rights reserved. * * Last Modification at: 3/12/15 4:34 PM */ package com.braunster.androidchatsdk.firebaseplugin.firebase; import android.content.Context; import android.support.annotation.NonNull; import com.braunster.androidchatsdk.firebaseplugin.R; import com.braunster.androidchatsdk.firebaseplugin.firebase.backendless.BBackendlessHandler; import com.braunster.androidchatsdk.firebaseplugin.firebase.backendless.ChatSDKReceiver; import com.braunster.chatsdk.Utils.Debug; import com.braunster.chatsdk.dao.BMessage; import com.braunster.chatsdk.dao.BThread; import com.braunster.chatsdk.dao.BUser; import com.braunster.chatsdk.dao.core.DaoCore; import com.braunster.chatsdk.network.AbstractNetworkAdapter; import com.braunster.chatsdk.network.BDefines; import com.braunster.chatsdk.network.BFacebookManager; import com.braunster.chatsdk.network.BFirebaseDefines; import com.braunster.chatsdk.network.TwitterManager; import com.braunster.chatsdk.object.BError; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.firebase.auth.AuthCredential; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FacebookAuthProvider; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.TwitterAuthProvider; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.ValueEventListener; import org.jdeferred.Deferred; import org.jdeferred.DoneCallback; import org.jdeferred.FailCallback; import org.jdeferred.Promise; import org.jdeferred.impl.DeferredObject; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import timber.log.Timber; import static com.braunster.chatsdk.dao.entities.BMessageEntity.Type.IMAGE; import static com.braunster.chatsdk.dao.entities.BMessageEntity.Type.LOCATION; import static com.braunster.chatsdk.network.BDefines.BAccountType.Anonymous; import static com.braunster.chatsdk.network.BDefines.BAccountType.Custom; import static com.braunster.chatsdk.network.BDefines.BAccountType.Facebook; import static com.braunster.chatsdk.network.BDefines.BAccountType.Password; import static com.braunster.chatsdk.network.BDefines.BAccountType.Register; import static com.braunster.chatsdk.network.BDefines.BAccountType.Twitter; import static com.braunster.chatsdk.network.BDefines.Keys; public abstract class BFirebaseNetworkAdapter extends AbstractNetworkAdapter { private static final String TAG = BFirebaseNetworkAdapter.class.getSimpleName(); private static boolean DEBUG = Debug.BFirebaseNetworkAdapter; public BFirebaseNetworkAdapter(Context context){ super(context); // Adding the manager that will handle all the incoming events. FirebaseEventsManager eventManager = FirebaseEventsManager.getInstance(); setEventManager(eventManager); // Setting the upload handler setUploadHandler(new BFirebaseUploadHandler()); // Setting the push handler BBackendlessHandler backendlessPushHandler = new BBackendlessHandler(); backendlessPushHandler.setContext(context); setPushHandler(backendlessPushHandler); // Parse init /*Parse.initialize(context, context.getString(R.string.parse_app_id), context.getString(R.string.parse_client_key)); ParseInstallation.getCurrentInstallation().saveInBackground();*/ backendlessPushHandler.initWithAppKey(context.getString(R.string.backendless_app_id), context.getString(R.string.backendless_secret_key), context.getString(R.string.backendless_app_version)); } /** * Indicator for the current state of the authentication process. **/ protected enum AuthStatus{ IDLE { @Override public String toString() { return "Idle"; } }, AUTH_WITH_MAP{ @Override public String toString() { return "Auth with map"; } }, HANDLING_F_USER{ @Override public String toString() { return "Handling F user"; } }, UPDATING_USER{ @Override public String toString() { return "Updating user"; } }, PUSHING_USER{ @Override public String toString() { return "Pushing user"; } }, CHECKING_IF_AUTH{ @Override public String toString() { return "Checking if Authenticated"; } } } protected AuthStatus authingStatus = AuthStatus.IDLE; public AuthStatus getAuthingStatus() { return authingStatus; } public boolean isAuthing(){ return authingStatus != AuthStatus.IDLE; } protected void resetAuth(){ authingStatus = AuthStatus.IDLE; } @Override public Promise<Object, BError, Void> authenticateWithMap(final Map<String, Object> details) { if (DEBUG) Timber.v("authenticateWithMap, KeyType: %s", details.get(BDefines.Prefs.LoginTypeKey)); final Deferred<Object, BError, Void> deferred = new DeferredObject<>(); if (isAuthing()) { if (DEBUG) Timber.d("Already Authing!, Status: %s", authingStatus.name()); deferred.reject(BError.getError(BError.Code.AUTH_IN_PROCESS, "Cant run two auth in parallel")); return deferred.promise(); } authingStatus = AuthStatus.AUTH_WITH_MAP; OnCompleteListener<AuthResult> resultHandler = new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull final Task<AuthResult> task) { if(task.isComplete() && task.isSuccessful()) { handleFAUser(task.getResult().getUser()).then(new DoneCallback<BUser>() { @Override public void onDone(BUser bUser) { resetAuth(); deferred.resolve(task.getResult().getUser()); resetAuth(); } }, new FailCallback<BError>() { @Override public void onFail(BError bError) { resetAuth(); deferred.reject(bError); } }); } else { if (DEBUG) Timber.e("Error login in, Name: %s", task.getException().getMessage()); resetAuth(); deferred.reject(BError.getExceptionError(task.getException())); } } }; AuthCredential credential = null; switch ((Integer)details.get(BDefines.Prefs.LoginTypeKey)) { case Facebook: if (DEBUG) Timber.d(TAG, "authing with fb, AccessToken: %s", BFacebookManager.userFacebookAccessToken); AbstractNetworkAdapter.provider = BDefines.ProviderString.Facebook; AbstractNetworkAdapter.token = BFacebookManager.userFacebookAccessToken; credential = FacebookAuthProvider.getCredential(BFacebookManager.userFacebookAccessToken); FirebaseAuth.getInstance().signInWithCredential(credential).addOnCompleteListener(resultHandler); break; case Twitter: if (DEBUG) Timber.d("authing with twitter, AccessToken: %s", TwitterManager.accessToken.getToken()); AbstractNetworkAdapter.provider = BDefines.ProviderString.Twitter; AbstractNetworkAdapter.token = TwitterManager.accessToken.getToken(); credential = TwitterAuthProvider.getCredential(TwitterManager.accessToken.getToken(), TwitterManager.accessToken.getSecret()); FirebaseAuth.getInstance().signInWithCredential(credential).addOnCompleteListener(resultHandler); break; case Password: AbstractNetworkAdapter.provider = BDefines.ProviderString.Password; FirebaseAuth.getInstance().signInWithEmailAndPassword((String) details.get(BDefines.Prefs.LoginEmailKey), (String) details.get(BDefines.Prefs.LoginPasswordKey)).addOnCompleteListener(resultHandler); break; case Register: FirebaseAuth.getInstance().createUserWithEmailAndPassword((String) details.get(BDefines.Prefs.LoginEmailKey), (String) details.get(BDefines.Prefs.LoginPasswordKey)).addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { // Resetting so we could auth again. resetAuth(); if(task.isSuccessful()) { //Authing the user after creating it. details.put(BDefines.Prefs.LoginTypeKey, Password); authenticateWithMap(details).done(new DoneCallback<Object>() { @Override public void onDone(Object o) { deferred.resolve(o); } }).fail(new FailCallback<BError>() { @Override public void onFail(BError bError) { deferred.reject(bError); } }); } else { if (DEBUG) Timber.e("Error login in, Name: %s", task.getException().getMessage()); resetAuth(); deferred.reject(getFirebaseError(DatabaseError.fromException(task.getException()))); } } }); break; case Anonymous: AbstractNetworkAdapter.provider = BDefines.ProviderString.Anonymous; FirebaseAuth.getInstance().signInAnonymously().addOnCompleteListener(resultHandler); break; case Custom: AbstractNetworkAdapter.provider = BDefines.ProviderString.Custom; FirebaseAuth.getInstance().signInWithCustomToken((String) details.get(BDefines.Prefs.TokenKey)).addOnCompleteListener(resultHandler); break; default: if (DEBUG) Timber.d("No login type was found"); deferred.reject(BError.getError(BError.Code.NO_LOGIN_TYPE, "No matching login type was found")); break; } return deferred.promise(); } public abstract Promise<BUser, BError, Void> handleFAUser(final FirebaseUser authData); @Override public String getServerURL() { return BDefines.ServerUrl; } protected void pushForMessage(final BMessage message){ if (!backendlessEnabled()) return; if (DEBUG) Timber.v("pushForMessage"); if (message.getThread().getTypeSafely() == BThread.Type.Private) { // Loading the message from firebase to get the timestamp from server. DatabaseReference firebase = FirebasePaths.threadRef(message.getThread().getEntityID()) .child(BFirebaseDefines.Path.BMessagesPath) .child(message.getEntityID()); firebase.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { Long date = null; try { date = (Long) snapshot.child(Keys.BDate).getValue(); } catch (ClassCastException e) { date = (((Double)snapshot.child(Keys.BDate).getValue()).longValue()); } finally { if (date != null) { message.setDate(new Date(date)); DaoCore.updateEntity(message); } } // If we failed to get date dont push. if (message.getDate()==null) return; BUser currentUser = currentUserModel(); List<BUser> users = new ArrayList<BUser>(); for (BUser user : message.getThread().getUsers()) if (!user.equals(currentUser)) if (!user.equals(currentUser)) { // Timber.v(user.getEntityID() + ", " + user.getOnline().toString()); // sends push notification regardless of receiver online status // TODO: add observer to online status // if (user.getOnline() == null || !user.getOnline()) users.add(user); } pushToUsers(message, users); } @Override public void onCancelled(DatabaseError firebaseError) { } }); } } protected void pushToUsers(BMessage message, List<BUser> users){ if (DEBUG) Timber.v("pushToUsers"); if (!backendlessEnabled() || users.size() == 0) return; // We're identifying each user using push channels. This means that // when a user signs up, they register with backendless on a particular // channel. In this case user_[user id] this means that we can // send a push to a specific user if we know their user id. List<String> channels = new ArrayList<String>(); for (BUser user : users) channels.add(user.getPushChannel()); if (DEBUG) Timber.v("pushutils sendmessage"); String messageText = message.getText(); if (message.getType() == LOCATION) messageText = "Location Message"; else if (message.getType() == IMAGE) messageText = "Picture Message"; String sender = message.getBUserSender().getMetaName(); String fullText = sender + " " + messageText; JSONObject data = new JSONObject(); try { data.put(BDefines.Keys.ACTION, ChatSDKReceiver.ACTION_MESSAGE); data.put(BDefines.Keys.CONTENT, fullText); data.put(BDefines.Keys.MESSAGE_ENTITY_ID, message.getEntityID()); data.put(BDefines.Keys.THREAD_ENTITY_ID, message.getThread().getEntityID()); data.put(BDefines.Keys.MESSAGE_DATE, message.getDate().getTime()); data.put(BDefines.Keys.MESSAGE_SENDER_ENTITY_ID, message.getBUserSender().getEntityID()); data.put(BDefines.Keys.MESSAGE_SENDER_NAME, message.getBUserSender().getMetaName()); data.put(BDefines.Keys.MESSAGE_TYPE, message.getType()); data.put(BDefines.Keys.MESSAGE_PAYLOAD, message.getText()); //For iOS data.put(BDefines.Keys.BADGE, BDefines.Keys.INCREMENT); data.put(BDefines.Keys.ALERT, fullText); // For making sound in iOS data.put(BDefines.Keys.SOUND, BDefines.Keys.Default); } catch (JSONException e) { e.printStackTrace(); } pushHandler.pushToChannels(channels, data); } /** Convert the firebase error to a {@link com.braunster.chatsdk.object.BError BError} object. */ public static BError getFirebaseError(DatabaseError error){ String errorMessage = ""; int code = 0; switch (error.getCode()) { /*case DatabaseError.EMAIL_TAKEN: code = BError.Code.EMAIL_TAKEN; errorMessage = "Email is taken."; break; case DatabaseError.INVALID_EMAIL: code = BError.Code.INVALID_EMAIL; errorMessage = "Invalid Email."; break; case DatabaseError.INVALID_PASSWORD: code = BError.Code.INVALID_PASSWORD; errorMessage = "Invalid Password"; break; case DatabaseError.USER_DOES_NOT_EXIST: code = BError.Code.USER_DOES_NOT_EXIST; errorMessage = "Account not found."; break; case DatabaseError.INVALID_CREDENTIALS: code = BError.Code.INVALID_CREDENTIALS; errorMessage = "Invalid credentials."; break;*/ case DatabaseError.NETWORK_ERROR: code = BError.Code.NETWORK_ERROR; errorMessage = "Network Error."; break; case DatabaseError.EXPIRED_TOKEN: code = BError.Code.EXPIRED_TOKEN; errorMessage = "Expired Token."; break; case DatabaseError.OPERATION_FAILED: code = BError.Code.OPERATION_FAILED; errorMessage = "Operation failed"; break; case DatabaseError.PERMISSION_DENIED: code = BError.Code.PERMISSION_DENIED; errorMessage = "Permission denied"; break; case DatabaseError.DISCONNECTED: code = BError.Code.DISCONNECTED; errorMessage = "Disconnected."; break; case DatabaseError.INVALID_TOKEN: code = BError.Code.INVALID_TOKEN; errorMessage = "Invalid token."; break; case DatabaseError.MAX_RETRIES: code = BError.Code.MAX_RETRIES; errorMessage = "Max retries."; break; case DatabaseError.OVERRIDDEN_BY_SET: code = BError.Code.OVERRIDDEN_BY_SET; errorMessage = "Overridden by set."; break; case DatabaseError.UNAVAILABLE: code = BError.Code.UNAVAILABLE; errorMessage = "Unavailable."; break; case DatabaseError.UNKNOWN_ERROR: code = BError.Code.UNKNOWN_ERROR; errorMessage = "Unknown error."; break; case DatabaseError.USER_CODE_EXCEPTION: code = BError.Code.USER_CODE_EXCEPTION; String[] stacktrace = error.toException().getMessage().split(": "); String[] message = stacktrace[2].split("\\."); errorMessage = message[0]; break; case DatabaseError.WRITE_CANCELED: code = BError.Code.WRITE_CANCELED; errorMessage = "Write canceled."; break; default: errorMessage = "An Error Occurred."; } return new BError(code, errorMessage, error); } }