package org.awesomeapp.messenger.tasks; import android.app.Activity; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import org.awesomeapp.messenger.ImApp; import org.awesomeapp.messenger.crypto.otr.OtrAndroidKeyManagerImpl; import org.awesomeapp.messenger.model.ImConnection; import org.awesomeapp.messenger.model.ImErrorInfo; import org.awesomeapp.messenger.provider.Imps; import org.awesomeapp.messenger.service.IChatSession; import org.awesomeapp.messenger.service.IChatSessionManager; import org.awesomeapp.messenger.service.IContactList; import org.awesomeapp.messenger.service.IContactListManager; import org.awesomeapp.messenger.service.IImConnection; import org.awesomeapp.messenger.service.adapters.ChatSessionAdapter; import org.awesomeapp.messenger.ui.legacy.DatabaseUtils; import org.awesomeapp.messenger.ui.legacy.SignInHelper; import org.awesomeapp.messenger.ui.legacy.SimpleAlertHandler; import org.awesomeapp.messenger.ui.onboarding.OnboardingAccount; import org.awesomeapp.messenger.ui.onboarding.OnboardingActivity; import org.awesomeapp.messenger.ui.onboarding.OnboardingManager; import org.awesomeapp.messenger.util.AssetUtil; import org.json.JSONException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.KeyPair; import java.util.ArrayList; import java.util.Collection; import java.util.List; import im.zom.messenger.R; /** * Created by n8fr8 on 5/1/17. */ public class MigrateAccountTask extends AsyncTask<String, Void, OnboardingAccount> { Activity mContext; IImConnection mConn; long mAccountId; long mProviderId; ImApp mApp; IImConnection mNewConn; OnboardingAccount mNewAccount; MigrateAccountListener mListener; Handler mHandler = new Handler(); ArrayList<String> mContacts; public MigrateAccountTask(Activity context, ImApp app, long providerId, long accountId, MigrateAccountListener listener) { mContext = context; mAccountId = accountId; mProviderId = providerId; mApp = app; mListener = listener; mConn = app.getConnection(providerId, accountId); mContacts = new ArrayList<>(); } @Override protected OnboardingAccount doInBackground(String... newDomains) { //get existing account username String nickname = Imps.Account.getNickname(mContext.getContentResolver(), mAccountId); String username = Imps.Account.getUserName(mContext.getContentResolver(), mAccountId); String password = Imps.Account.getPassword(mContext.getContentResolver(), mAccountId); OtrAndroidKeyManagerImpl keyMan = OtrAndroidKeyManagerImpl.getInstance(mContext); KeyPair keyPair = keyMan.generateLocalKeyPair(); String fingerprint = keyMan.getFingerprint(keyPair.getPublic()); //find or use provided new server/domain String domain = newDomains[0]; //register account on new domain with same password mNewAccount = registerNewAccount(nickname, username, password, domain); if (mNewAccount == null) { username = username + '.' + fingerprint.substring(fingerprint.length()-8,fingerprint.length()).toLowerCase(); mNewAccount = registerNewAccount(nickname, username, password, domain); if (mNewAccount == null) return null; } String newJabberId = mNewAccount.username + '@' + mNewAccount.domain; keyMan.storeKeyPair(newJabberId,keyPair); //send migration message to existing contacts and/or sessions try { String inviteLink = OnboardingManager.generateInviteLink(mContext,newJabberId,fingerprint,nickname); String migrateMessage = mContext.getString(R.string.migrate_message) + ' ' + inviteLink; IChatSessionManager sessionMgr = mConn.getChatSessionManager(); //login and set new default account SignInHelper signInHelper = new SignInHelper(mContext, mHandler); signInHelper.activateAccount(mNewAccount.providerId, mNewAccount.accountId); signInHelper.signIn(mNewAccount.password, mNewAccount.providerId, mNewAccount.accountId, true); mNewConn = mApp.getConnection(mNewAccount.providerId, mNewAccount.accountId); while(mNewConn.getState() != ImConnection.LOGGED_IN) { try { Thread.sleep(500);} catch (Exception e){} } List<IContactList> listOfLists = mConn.getContactListManager().getContactLists(); for (IContactList contactList : listOfLists) { //IContactList contactList = mConn.getContactListManager().getContactList(listName); String[] contacts = contactList.getContacts(); for (String contact : contacts) { mContacts.add(contact); IChatSession session = sessionMgr.getChatSession(contact); if (session == null) { session = sessionMgr.createChatSession(contact, true); } if (!session.isEncrypted()) { //try to kick off some encryption here session.getDefaultOtrChatSession().startChatEncryption(); try { Thread.sleep(500);} //just wait a half second here? catch (Exception e){} } session.sendMessage(migrateMessage, false); } } for (String contact : mContacts) { addToContactList(mNewConn, contact, keyMan.getRemoteFingerprint(contact), null); } //archive existing conversations and contacts List<IChatSession> listSession = mConn.getChatSessionManager().getActiveChatSessions(); for (IChatSession session : listSession) { session.leave(); } migrateAvatars(username, newJabberId); mConn.broadcastMigrationIdentity(newJabberId); mApp.setDefaultAccount(mNewAccount.providerId,mNewAccount.accountId); //logout of existing account setKeepSignedIn(mAccountId, false); mConn.logout(); return mNewAccount; } catch (Exception e) { Log.e(ImApp.LOG_TAG,"error with migration",e); } //failed return null; } @Override protected void onPostExecute(OnboardingAccount account) { super.onPostExecute(account); if (account == null) { if (mListener != null) mListener.migrateFailed(mProviderId,mAccountId); } else { if (mListener != null) mListener.migrateComplete(account); } } private OnboardingAccount registerNewAccount (String nickname, String username, String password, String domain) { try { OnboardingAccount result = OnboardingManager.registerAccount(mContext, nickname, username, password, domain, 5222); return result; } catch (JSONException jse) { } return null; } private int addToContactList (IImConnection conn, String address, String otrFingperint, String nickname) { int res = -1; try { IContactList list = getContactList(conn); if (list != null) { res = list.addContact(address, nickname); if (res != ImErrorInfo.NO_ERROR) { //what to do here? } if (!TextUtils.isEmpty(otrFingperint)) { OtrAndroidKeyManagerImpl.getInstance(mApp).verifyUser(address, otrFingperint); } //Contact contact = new Contact(new XmppAddress(address),address); //IContactListManager contactListMgr = conn.getContactListManager(); //contactListMgr.approveSubscription(contact); } } catch (RemoteException re) { Log.e(ImApp.LOG_TAG, "error adding contact", re); } return res; } private IContactList getContactList(IImConnection conn) { if (conn == null) { return null; } try { IContactListManager contactListMgr = conn.getContactListManager(); // Use the default list List<IBinder> lists = contactListMgr.getContactLists(); for (IBinder binder : lists) { IContactList list = IContactList.Stub.asInterface(binder); if (list.isDefault()) { return list; } } // No default list, use the first one as default list if (!lists.isEmpty()) { return IContactList.Stub.asInterface(lists.get(0)); } return null; } catch (RemoteException e) { // If the service has died, there is no list for now. return null; } } public interface MigrateAccountListener { public void migrateComplete (OnboardingAccount account); public void migrateFailed (long providerId, long accountId); } private void setKeepSignedIn(final long accountId, boolean signin) { Uri mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); ContentValues values = new ContentValues(); values.put(Imps.Account.KEEP_SIGNED_IN, signin); mContext.getContentResolver().update(mAccountUri, values, null, null); } private void migrateAvatars (String oldUsername, String newUsername) { try { //first copy the old avatar over to the new account byte[] oldAvatar = DatabaseUtils.getAvatarBytesFromAddress(mContext.getContentResolver(),oldUsername); if (oldAvatar != null) { setAvatar(newUsername, oldAvatar); } //now change the older avatar, so the vcard gets reloaded Bitmap bitmap = BitmapFactory.decodeStream(mContext.getAssets().open("stickers/olo and shimi/4greeting.png")); setAvatar(oldUsername, bitmap); } catch (Exception ioe) { ioe.printStackTrace(); } } private void setAvatar(String address, Bitmap bmp) { try { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.JPEG, 90, stream); byte[] avatarBytesCompressed = stream.toByteArray(); String avatarHash = "nohash"; DatabaseUtils.insertAvatarBlob(mContext.getContentResolver(), Imps.Avatars.CONTENT_URI, mProviderId, mAccountId, avatarBytesCompressed, avatarHash, address); } catch (Exception e) { Log.w(ImApp.LOG_TAG, "error loading image bytes", e); } } private void setAvatar(String address, byte[] avatarBytesCompressed) { try { String avatarHash = "nohash"; DatabaseUtils.insertAvatarBlob(mContext.getContentResolver(), Imps.Avatars.CONTENT_URI, mProviderId, mAccountId, avatarBytesCompressed, avatarHash, address); } catch (Exception e) { Log.w(ImApp.LOG_TAG, "error loading image bytes", e); } } }