/*
* Copyright (C) 2007-2008 Esmertec AG. Copyright (C) 2007-2008 The Android Open
* Source Project
*
* 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 org.awesomeapp.messenger;
import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.multidex.MultiDexApplication;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;
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.provider.ImpsProvider;
import org.awesomeapp.messenger.push.PushManager;
import org.awesomeapp.messenger.push.model.PersistedAccount;
import org.awesomeapp.messenger.service.Broadcaster;
import org.awesomeapp.messenger.service.IChatSession;
import org.awesomeapp.messenger.service.IChatSessionManager;
import org.awesomeapp.messenger.service.IConnectionCreationListener;
import org.awesomeapp.messenger.service.IImConnection;
import org.awesomeapp.messenger.service.IRemoteImService;
import org.awesomeapp.messenger.service.ImServiceConstants;
import org.awesomeapp.messenger.service.RemoteImService;
import org.awesomeapp.messenger.service.StatusBarNotifier;
import org.awesomeapp.messenger.tasks.MigrateAccountTask;
import org.awesomeapp.messenger.ui.legacy.ImPluginHelper;
import org.awesomeapp.messenger.ui.legacy.ProviderDef;
import org.awesomeapp.messenger.ui.legacy.adapter.ConnectionListenerAdapter;
import org.awesomeapp.messenger.util.Debug;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import im.zom.messenger.BuildConfig;
import info.guardianproject.cacheword.CacheWordHandler;
import info.guardianproject.cacheword.ICacheWordSubscriber;
import info.guardianproject.cacheword.PRNGFixes;
import info.guardianproject.iocipher.VirtualFileSystem;
import im.zom.messenger.R;
import timber.log.Timber;
import org.awesomeapp.messenger.util.Languages;
import org.chatsecure.pushsecure.PushSecureClient;
import org.chatsecure.pushsecure.response.Account;
import org.ironrabbit.type.CustomTypefaceManager;
import org.jivesoftware.smackx.omemo.OmemoInitializer;
public class ImApp extends MultiDexApplication implements ICacheWordSubscriber {
public static final String LOG_TAG = "Zom";
public static final String EXTRA_INTENT_SEND_TO_USER = "Send2_U";
public static final String EXTRA_INTENT_PASSWORD = "password";
public static final String EXTRA_INTENT_PROXY_TYPE = "proxy.type";
public static final String EXTRA_INTENT_PROXY_HOST = "proxy.host";
public static final String EXTRA_INTENT_PROXY_PORT = "proxy.port";
public static final String IMPS_CATEGORY = "org.awesomeapp.messenger.service.IMPS_CATEGORY";
public static final String ACTION_QUIT = "org.awesomeapp.messenger.service.QUIT";
public static final int SMALL_AVATAR_WIDTH = 48;
public static final int SMALL_AVATAR_HEIGHT = 48;
public static final int DEFAULT_AVATAR_WIDTH = 196;
public static final int DEFAULT_AVATAR_HEIGHT = 196;
public static final String HOCKEY_APP_ID = "3cd4c5ff8b666e25466d3b8b66f31766";
public static final String DEFAULT_TIMEOUT_CACHEWORD = "-1"; //one day
public static final String CACHEWORD_PASSWORD_KEY = "pkey";
public static final String CLEAR_PASSWORD_KEY = "clear_key";
public static final String NO_CREATE_KEY = "nocreate";
public static final String PREFERENCE_KEY_TEMP_PASS = "temppass";
//ACCOUNT SETTINGS Imps defaults
public static final String DEFAULT_XMPP_RESOURCE = "ChatSecureZom";
public static final int DEFAULT_XMPP_PRIORITY = 20;
public static final String DEFAULT_XMPP_OTR_MODE = "auto";
public final static String URL_UPDATER = "https://raw.githubusercontent.com/zom/Zom-Android/master/appupdater.xml";
public final static String ZOM_SERVICES_ADDRESS = "zombot@home.zom.im";
private Locale locale = null;
public static ImApp sImApp;
IRemoteImService mImService;
// HashMap<Long, IImConnection> mConnections;
MyConnListener mConnectionListener;
HashMap<Long, ProviderDef> mProviders;
Broadcaster mBroadcaster;
/**
* A queue of messages that are waiting to be sent when service is
* connected.
*/
ArrayList<Message> mQueue = new ArrayList<Message>();
/** A flag indicates that we have called tomServiceStarted start the service. */
// private boolean mServiceStarted;
private Context mApplicationContext;
PushManager mPushManager;
boolean mSupportPushReceive = false;
private CacheWordHandler mCacheWord;
public static Executor sThreadPoolExecutor = null;
private SharedPreferences settings = null;
private boolean mThemeDark = false;
private boolean mNeedsAccountUpgrade = false;
public static final int EVENT_SERVICE_CONNECTED = 100;
public static final int EVENT_CONNECTION_CREATED = 150;
public static final int EVENT_CONNECTION_LOGGING_IN = 200;
public static final int EVENT_CONNECTION_LOGGED_IN = 201;
public static final int EVENT_CONNECTION_LOGGING_OUT = 202;
public static final int EVENT_CONNECTION_DISCONNECTED = 203;
public static final int EVENT_CONNECTION_SUSPENDED = 204;
public static final int EVENT_USER_PRESENCE_UPDATED = 300;
public static final int EVENT_UPDATE_USER_PRESENCE_ERROR = 301;
private static final String[] PROVIDER_PROJECTION = { Imps.Provider._ID, Imps.Provider.NAME,
Imps.Provider.FULLNAME,
Imps.Provider.SIGNUP_URL, };
private static final String[] ACCOUNT_PROJECTION = { Imps.Account._ID, Imps.Account.PROVIDER,
Imps.Account.NAME, Imps.Account.USERNAME,
Imps.Account.PASSWORD, };
static final void log(String log) {
Log.d(LOG_TAG, log);
}
/**
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}*/
@Override
public ContentResolver getContentResolver() {
if (mApplicationContext == null || mApplicationContext == this) {
return super.getContentResolver();
}
return mApplicationContext.getContentResolver();
}
@Override
public void onCreate() {
super.onCreate();
Preferences.setup(this);
Languages.setup(MainActivity.class, R.string.use_system_default);
Languages.setLanguage(this, Preferences.getLanguage(), false);
sImApp = this;
settings = PreferenceManager.getDefaultSharedPreferences(this);
Debug.onAppStart();
PRNGFixes.apply(); //Google's fix for SecureRandom bug: http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
// load these libs up front to shorten the delay after typing the passphrase
SQLiteDatabase.loadLibs(getApplicationContext());
VirtualFileSystem.get().isMounted();
// mConnections = new HashMap<Long, IImConnection>();
mApplicationContext = this;
//initTrustManager();
mBroadcaster = new Broadcaster();
setAppTheme(null,null);
// ChatSecure-Push needs to do initial setup as soon as Cacheword is ready
mCacheWord = new CacheWordHandler(this, this);
mCacheWord.connectToService();
if (sThreadPoolExecutor == null) {
int corePoolSize = 20;
int maximumPoolSize = 120;
int keepAliveTime = 30;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(maximumPoolSize);
sThreadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue);
}
}
public boolean isThemeDark ()
{
return mThemeDark;
}
public void setAppTheme (Activity activity)
{
setAppTheme(activity, null);
}
public void setAppTheme (Activity activity, Toolbar toolbar)
{
mThemeDark = settings.getBoolean("themeDark", false);
if (mThemeDark)
{
setTheme(R.style.AppThemeDark);
if (activity != null)
{
activity.setTheme(R.style.AppThemeDark);
}
}
else
{
setTheme(R.style.AppTheme);
if (activity != null)
{
activity.setTheme(R.style.AppTheme);
}
}
Configuration config = getResources().getConfiguration();
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
if (mImService != null)
{
boolean debugOn = settings.getBoolean("prefDebug", false);
try {
mImService.enableDebugLogging(debugOn);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public static void resetLanguage(Activity activity, String language) {
if (!TextUtils.equals(language, Preferences.getLanguage())) {
/* Set the preference after setting the locale in case something goes
* wrong. If setting the locale causes an Exception, it should not be set in
* the preferences, otherwise this will be stuck in a crash loop. */
Languages.setLanguage(activity, language, true);
Preferences.setLanguage(language);
Languages.forceChangeLanguage(activity);
CustomTypefaceManager.loadFromAssets(activity,language.equals("bo"));
}
}
public synchronized void startImServiceIfNeed() {
startImServiceIfNeed(false);
}
public synchronized void startImServiceIfNeed(boolean isBoot) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("start ImService");
if (mImService == null) {
Intent serviceIntent = new Intent(this, RemoteImService.class);
// serviceIntent.putExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, isBoot);
mApplicationContext.startService(serviceIntent);
mConnectionListener = new MyConnListener(new Handler());
mApplicationContext
.bindService(serviceIntent, mImServiceConn, Context.BIND_AUTO_CREATE);
}
}
public boolean hasActiveConnections ()
{
try {
return !mImService.getActiveConnections().isEmpty();
}
catch (RemoteException re)
{
return false;
}
}
public void stopImServiceIfInactive() {
//todo we don't wnat to do this right now
/**
if (!hasActiveConnections()) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("stop ImService because there's no active connections");
forceStopImService();
}*/
}
public void forceStopImService() {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("stop ImService");
if (mImServiceConn != null) {
try {
if (mImService != null)
mImService.shutdownAndLock();
}
catch (RemoteException re)
{
}
mApplicationContext.unbindService(mImServiceConn);
mImService = null;
}
Intent serviceIntent = new Intent(this, RemoteImService.class);
serviceIntent.putExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, true);
mApplicationContext.stopService(serviceIntent);
}
private ServiceConnection mImServiceConn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("service connected");
mImService = IRemoteImService.Stub.asInterface(service);
// fetchActiveConnections();
synchronized (mQueue) {
for (Message msg : mQueue) {
msg.sendToTarget();
}
mQueue.clear();
}
Message msg = Message.obtain(null, EVENT_SERVICE_CONNECTED);
mBroadcaster.broadcast(msg);
/*
if (mKillServerOnStart)
{
forceStopImService();
}*/
}
public void onServiceDisconnected(ComponentName className) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("service disconnected");
// mConnections.clear();
mImService = null;
}
};
public boolean serviceConnected() {
return mImService != null;
}
public static long insertOrUpdateAccount(ContentResolver cr, long providerId, long accountId, String nickname, String username,
String pw) {
String selection = Imps.Account.PROVIDER + "=? AND (" + Imps.Account._ID + "=?" + " OR " + Imps.Account.USERNAME + "=?)";
String[] selectionArgs = { Long.toString(providerId), Long.toString(accountId), username };
Cursor c = cr.query(Imps.Account.CONTENT_URI, ACCOUNT_PROJECTION, selection, selectionArgs,
null);
if (c != null && c.moveToFirst()) {
long id = c.getLong(c.getColumnIndexOrThrow(Imps.Account._ID));
ContentValues values = new ContentValues(4);
values.put(Imps.Account.PROVIDER, providerId);
if (!TextUtils.isEmpty(nickname))
values.put(Imps.Account.NAME, nickname);
if (!TextUtils.isEmpty(username))
values.put(Imps.Account.USERNAME, username);
if (!TextUtils.isEmpty(pw))
values.put(Imps.Account.PASSWORD, pw);
Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, id);
cr.update(accountUri, values, null, null);
c.close();
return id;
} else {
ContentValues values = new ContentValues(4);
values.put(Imps.Account.PROVIDER, providerId);
values.put(Imps.Account.NAME, nickname);
values.put(Imps.Account.USERNAME, username);
values.put(Imps.Account.PASSWORD, pw);
if (pw != null && pw.length() > 0) {
values.put(Imps.Account.KEEP_SIGNED_IN, true);
}
Uri result = cr.insert(Imps.Account.CONTENT_URI, values);
if (c != null)
c.close();
return ContentUris.parseId(result);
}
}
private void loadImProviderSettings() {
mProviders = new HashMap<Long, ProviderDef>();
ContentResolver cr = getContentResolver();
String selectionArgs[] = new String[1];
selectionArgs[0] = ImApp.IMPS_CATEGORY;
Cursor c = cr.query(Imps.Provider.CONTENT_URI, PROVIDER_PROJECTION, Imps.Provider.CATEGORY
+ "=?", selectionArgs,
null);
if (c == null) {
return;
}
try {
while (c.moveToNext()) {
long id = c.getLong(0);
String providerName = c.getString(1);
String fullName = c.getString(2);
String signUpUrl = c.getString(3);
if (mProviders == null) // mProviders has been reset
break;
mProviders.put(id, new ProviderDef(id, providerName, fullName, signUpUrl));
}
} finally {
c.close();
}
}
public long getProviderId(String name) {
loadImProviderSettings();
for (ProviderDef provider : mProviders.values()) {
if (provider.mName.equals(name)) {
return provider.mId;
}
}
return -1;
}
public ProviderDef getProvider(long id) {
loadImProviderSettings();
return mProviders.get(id);
}
public IImConnection createConnection(long providerId, long accountId) throws RemoteException {
if (mImService == null) {
// Service hasn't been connected or has died.
return null;
}
IImConnection conn = mImService.createConnection(providerId, accountId);
return conn;
}
public IImConnection getConnection(long providerId,long accountId) {
try {
if (mImService != null) {
IImConnection im = mImService.getConnection(providerId);
if (im != null) {
im.getState();
} else {
im = createConnection(providerId, accountId);
}
return im;
}
else
return null;
}
catch (RemoteException re)
{
return null;
}
}
public Collection<IImConnection> getActiveConnections() {
try {
return mImService.getActiveConnections();
}
catch (RemoteException re)
{
return null;
}
}
public void callWhenServiceConnected(Handler target, Runnable callback) {
Message msg = Message.obtain(target, callback);
if (serviceConnected() && msg != null) {
msg.sendToTarget();
} else {
startImServiceIfNeed();
synchronized (mQueue) {
mQueue.add(msg);
}
}
}
public static void deleteAccount (ContentResolver resolver, long accountId, long providerId)
{
Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId);
resolver.delete(accountUri, null, null);
Uri providerUri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId);
resolver.delete(providerUri, null, null);
Uri.Builder builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon();
ContentUris.appendId(builder, providerId);
ContentUris.appendId(builder, accountId);
resolver.delete(builder.build(), null, null);
}
public void registerForBroadcastEvent(int what, Handler target) {
mBroadcaster.request(what, target, what);
}
public void unregisterForBroadcastEvent(int what, Handler target) {
mBroadcaster.cancelRequest(what, target, what);
}
public void registerForConnEvents(Handler handler) {
mBroadcaster.request(EVENT_CONNECTION_CREATED, handler, EVENT_CONNECTION_CREATED);
mBroadcaster.request(EVENT_CONNECTION_LOGGING_IN, handler, EVENT_CONNECTION_LOGGING_IN);
mBroadcaster.request(EVENT_CONNECTION_LOGGED_IN, handler, EVENT_CONNECTION_LOGGED_IN);
mBroadcaster.request(EVENT_CONNECTION_LOGGING_OUT, handler, EVENT_CONNECTION_LOGGING_OUT);
mBroadcaster.request(EVENT_CONNECTION_SUSPENDED, handler, EVENT_CONNECTION_SUSPENDED);
mBroadcaster.request(EVENT_CONNECTION_DISCONNECTED, handler, EVENT_CONNECTION_DISCONNECTED);
mBroadcaster.request(EVENT_USER_PRESENCE_UPDATED, handler, EVENT_USER_PRESENCE_UPDATED);
mBroadcaster.request(EVENT_UPDATE_USER_PRESENCE_ERROR, handler,
EVENT_UPDATE_USER_PRESENCE_ERROR);
}
public void unregisterForConnEvents(Handler handler) {
mBroadcaster.cancelRequest(EVENT_CONNECTION_CREATED, handler, EVENT_CONNECTION_CREATED);
mBroadcaster.cancelRequest(EVENT_CONNECTION_LOGGING_IN, handler,
EVENT_CONNECTION_LOGGING_IN);
mBroadcaster.cancelRequest(EVENT_CONNECTION_LOGGED_IN, handler, EVENT_CONNECTION_LOGGED_IN);
mBroadcaster.cancelRequest(EVENT_CONNECTION_LOGGING_OUT, handler,
EVENT_CONNECTION_LOGGING_OUT);
mBroadcaster.cancelRequest(EVENT_CONNECTION_SUSPENDED, handler, EVENT_CONNECTION_SUSPENDED);
mBroadcaster.cancelRequest(EVENT_CONNECTION_DISCONNECTED, handler,
EVENT_CONNECTION_DISCONNECTED);
mBroadcaster.cancelRequest(EVENT_USER_PRESENCE_UPDATED, handler,
EVENT_USER_PRESENCE_UPDATED);
mBroadcaster.cancelRequest(EVENT_UPDATE_USER_PRESENCE_ERROR, handler,
EVENT_UPDATE_USER_PRESENCE_ERROR);
}
void broadcastConnEvent(int what, long providerId, ImErrorInfo error) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
log("broadcasting connection event " + what + ", provider id " + providerId);
}
android.os.Message msg = android.os.Message.obtain(null, what, (int) (providerId >> 32),
(int) providerId, error);
mBroadcaster.broadcast(msg);
}
public void dismissChatNotification(long providerId, String username) {
if (mImService != null) {
try {
mImService.dismissChatNotification(providerId, username);
} catch (RemoteException e) {
}
}
}
/**
private void fetchActiveConnections() {
if (mImService != null)
{
try {
// register the listener before fetch so that we won't miss any connection.
mImService.addConnectionCreatedListener(mConnCreationListener);
synchronized (mConnections) {
for (IBinder binder : (List<IBinder>) mImService.getActiveConnections()) {
IImConnection conn = IImConnection.Stub.asInterface(binder);
long providerId = conn.getProviderId();
if (!mConnections.containsKey(providerId)) {
mConnections.put(providerId, conn);
conn.registerConnectionListener(mConnectionListener);
}
}
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "fetching active connections", e);
}
}
}*/
private final IConnectionCreationListener mConnCreationListener = new IConnectionCreationListener.Stub() {
public void onConnectionCreated(IImConnection conn) throws RemoteException {
long providerId = conn.getProviderId();
conn.registerConnectionListener(mConnectionListener);
/**
synchronized (mConnections) {
if (!mConnections.containsKey(providerId)) {
mConnections.put(providerId, conn);
conn.registerConnectionListener(mConnectionListener);
}
}*/
broadcastConnEvent(EVENT_CONNECTION_CREATED, providerId, null);
}
};
private final class MyConnListener extends ConnectionListenerAdapter {
public MyConnListener(Handler handler) {
super(handler);
}
@Override
public void onConnectionStateChange(IImConnection conn, int state, ImErrorInfo error) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
log("onConnectionStateChange(" + state + ", " + error + ")");
}
try {
//fetchActiveConnections();
int what = -1;
long providerId = conn.getProviderId();
switch (state) {
case ImConnection.LOGGED_IN:
what = EVENT_CONNECTION_LOGGED_IN;
break;
case ImConnection.LOGGING_IN:
what = EVENT_CONNECTION_LOGGING_IN;
break;
case ImConnection.LOGGING_OUT:
// NOTE: if this logic is changed, the logic in ImConnectionAdapter.ConnectionAdapterListener must be changed to match
what = EVENT_CONNECTION_LOGGING_OUT;
break;
case ImConnection.DISCONNECTED:
// NOTE: if this logic is changed, the logic in ImConnectionAdapter.ConnectionAdapterListener must be changed to match
what = EVENT_CONNECTION_DISCONNECTED;
// mConnections.remove(providerId);
// stop the service if there isn't an active connection anymore.
stopImServiceIfInactive();
break;
case ImConnection.SUSPENDED:
what = EVENT_CONNECTION_SUSPENDED;
break;
}
if (what != -1) {
broadcastConnEvent(what, providerId, error);
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "onConnectionStateChange", e);
}
}
@Override
public void onUpdateSelfPresenceError(IImConnection connection, ImErrorInfo error) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
log("onUpdateUserPresenceError(" + error + ")");
}
try {
long providerId = connection.getProviderId();
broadcastConnEvent(EVENT_UPDATE_USER_PRESENCE_ERROR, providerId, error);
} catch (RemoteException e) {
Log.e(LOG_TAG, "onUpdateUserPresenceError", e);
}
}
@Override
public void onSelfPresenceUpdated(IImConnection connection) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("onUserPresenceUpdated");
try {
long providerId = connection.getProviderId();
broadcastConnEvent(EVENT_USER_PRESENCE_UPDATED, providerId, null);
} catch (RemoteException e) {
Log.e(LOG_TAG, "onUserPresenceUpdated", e);
}
}
}
public IChatSession getChatSession(long providerId, long accountId, String remoteAddress) {
IImConnection conn = getConnection(providerId,accountId);
IChatSessionManager chatSessionManager = null;
if (conn != null) {
try {
chatSessionManager = conn.getChatSessionManager();
} catch (RemoteException e) {
Log.e(LOG_TAG, "error in getting ChatSessionManager", e);
}
}
if (chatSessionManager != null) {
try {
return chatSessionManager.getChatSession(remoteAddress);
} catch (RemoteException e) {
Log.e(LOG_TAG, "error in getting ChatSession", e);
}
}
return null;
}
public void maybeInit(Activity activity) {
startImServiceIfNeed();
setAppTheme(activity,null);
ImPluginHelper.getInstance(this).loadAvailablePlugins();
}
public boolean setDefaultAccount (long providerId, long accountId)
{
final Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT;
String[] PROVIDER_PROJECTION = {
Imps.Provider._ID,
Imps.Provider.ACTIVE_ACCOUNT_ID,
Imps.Provider.ACTIVE_ACCOUNT_USERNAME,
Imps.Provider.ACTIVE_ACCOUNT_NICKNAME
};
final Cursor cursorProviders = getContentResolver().query(uri, PROVIDER_PROJECTION,
Imps.Provider.ACTIVE_ACCOUNT_ID + "=" + accountId
+ " AND " + Imps.Provider.CATEGORY + "=?"
+ " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */,
new String[]{ImApp.IMPS_CATEGORY} /* selection args */,
Imps.Provider.DEFAULT_SORT_ORDER);
if (cursorProviders != null && cursorProviders.getCount() > 0) {
cursorProviders.moveToFirst();
mDefaultProviderId = cursorProviders.getLong(0);
mDefaultAccountId = cursorProviders.getLong(1);
mDefaultUsername = cursorProviders.getString(2);
mDefaultNickname = cursorProviders.getString(3);
settings.edit().putLong("defaultAccountId",mDefaultAccountId).commit();
Cursor pCursor = getContentResolver().query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(mDefaultProviderId)}, null);
Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(
pCursor, getContentResolver(), mDefaultProviderId, false /* don't keep updated */, null /* no handler */);
mDefaultUsername = mDefaultUsername + '@' + settings.getDomain();
mDefaultOtrFingerprint = OtrAndroidKeyManagerImpl.getInstance(this).getLocalFingerprint(mDefaultUsername);
settings.close();
cursorProviders.close();
return true;
}
if (cursorProviders != null)
cursorProviders.close();
return false;
}
public boolean initAccountInfo ()
{
long lastAccountId = settings.getLong("defaultAccountId",-1);
if (mDefaultProviderId == -1 || mDefaultAccountId == -1) {
final Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT;
String[] PROVIDER_PROJECTION = {
Imps.Provider._ID,
Imps.Provider.ACTIVE_ACCOUNT_ID,
Imps.Provider.ACTIVE_ACCOUNT_USERNAME,
Imps.Provider.ACTIVE_ACCOUNT_NICKNAME,
Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN
};
final Cursor cursorProviders = getContentResolver().query(uri, PROVIDER_PROJECTION,
Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */,
new String[]{ImApp.IMPS_CATEGORY} /* selection args */,
Imps.Provider.DEFAULT_SORT_ORDER);
if (cursorProviders != null && cursorProviders.getCount() > 0) {
while(cursorProviders.moveToNext()) {
long providerId = cursorProviders.getLong(0);
long accountId = cursorProviders.getLong(1);
String username = cursorProviders.getString(2);
String nickname = cursorProviders.getString(3);
boolean keepSignedIn = cursorProviders.getInt(4) != 0;
Cursor pCursor = getContentResolver().query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(providerId)}, null);
Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(
pCursor, getContentResolver(), providerId, false /* don't keep updated */, null /* no handler */);
username = username + '@' + settings.getDomain();
String otrFingerprint = OtrAndroidKeyManagerImpl.getInstance(this).getLocalFingerprint(username);
if ((!mNeedsAccountUpgrade)
&& settings.getDomain().equalsIgnoreCase("dukgo.com") && keepSignedIn)
{
mNeedsAccountUpgrade = true;
}
settings.close();
if (lastAccountId == -1 && keepSignedIn)
{
mDefaultProviderId = providerId;
mDefaultAccountId = accountId;
mDefaultUsername = username;
mDefaultNickname = nickname;
mDefaultOtrFingerprint = otrFingerprint;
}
else if (lastAccountId == accountId)
{
mDefaultProviderId = providerId;
mDefaultAccountId = accountId;
mDefaultUsername = username;
mDefaultNickname = nickname;
mDefaultOtrFingerprint = otrFingerprint;
}
else if (mDefaultProviderId == -1)
{
mDefaultProviderId = providerId;
mDefaultAccountId = accountId;
mDefaultUsername = username;
mDefaultNickname = nickname;
mDefaultOtrFingerprint = otrFingerprint;
}
}
}
if (cursorProviders != null)
cursorProviders.close();
}
return true;
}
private long mDefaultProviderId = -1;
private long mDefaultAccountId = -1;
private String mDefaultUsername = null;
private String mDefaultOtrFingerprint = null;
private String mDefaultNickname = null;
public String getDefaultUsername ()
{
return mDefaultUsername;
}
public String getDefaultNickname ()
{
return mDefaultNickname;
}
public String getDefaultOtrKey ()
{
return mDefaultOtrFingerprint;
}
public long getDefaultProviderId ()
{
return mDefaultProviderId;
}
public long getDefaultAccountId ()
{
return mDefaultAccountId;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Languages.setLanguage(this, Preferences.getLanguage(),true);
}
public void setupChatSecurePush() {
// Setup logging for ChatSecure-Push SDK
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}
Timber.d("SetupChatSecurePush");
mPushManager = new PushManager(this);
if (mSupportPushReceive) {
PersistedAccount chatSecurePushAccount = mPushManager.getPersistedAccount();
if (chatSecurePushAccount == null) {
Log.d(LOG_TAG, "No ChatSecure-Push Account is persisted. Creating new");
} else {
Log.d(LOG_TAG, "ChatSecure-Push Account is persisted with username: " + chatSecurePushAccount.username);
}
// Use the existing account credentials if available, else a new random username & password
final String username = isCspAccountValid(chatSecurePushAccount, mPushManager.getProviderUrl()) ?
chatSecurePushAccount.username :
UUID.randomUUID().toString().substring(0, 30); // ChatSecure-Push usernames are 30 characters max
final String password = isCspAccountValid(chatSecurePushAccount, mPushManager.getProviderUrl()) ?
chatSecurePushAccount.pasword :
UUID.randomUUID().toString();
final Object authLock = new Object();
final AtomicBoolean authenticated = new AtomicBoolean();
// Continue trying to authenticate until we have success
// Our free Heroku plan sometimes gives ya a SocketTimeout
while (!authenticated.get()) {
PushSecureClient.RequestCallback<Account> authCallback = new PushSecureClient.RequestCallback<Account>() {
@Override
public void onSuccess(@NonNull Account response) {
Log.d(LOG_TAG, "Registered ChatSecure-Push account!");
if (mCacheWord != null) {
mCacheWord.disconnectFromService();
mCacheWord = null;
}
authenticated.set(true);
synchronized (authLock) {
authLock.notify();
}
}
@Override
public void onFailure(@NonNull Throwable t) {
Log.e(LOG_TAG, "Failed to register ChatSecure-Push account!", t);
synchronized (authLock) {
authLock.notify();
}
}
};
// authenticateAccount will persist the account to our secure database if auth is successful
mPushManager.authenticateAccount(username, password, authCallback);
synchronized (authLock) {
try {
authLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public PushManager getPushManager() {
return mPushManager;
}
/**
* Reports whether the persisted ChatSecure-Push account is valid.
*
* @param account the persisted ChatSecure-Push account
* @param requestedProviderUrl the URL describing the desired ChatSecure-Push server instance
* where the user's account should be registered
* @return true if the given account is valid, false if a new account should be registered.
*/
private static boolean isCspAccountValid(PersistedAccount account,
@NonNull String requestedProviderUrl) {
return account != null && account.providerUrl.equals(requestedProviderUrl);
}
@Override
public void onCacheWordUninitialized() {
// unused
}
@Override
public void onCacheWordLocked() {
// unused
}
@Override
public void onCacheWordOpened() {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(LOG_TAG, "Awaiting ImpsProvider ready");
// Wait for ImpsProvider to initialize : it listens to onCacheWordOpened as well...
ImpsProvider.awaitDataReady();
Log.d(LOG_TAG, "ImpsProvider ready");
// setupChatSecurePush will disconnect the CacheWordHandler when it's done
setupChatSecurePush();
}
}).start();
}
public boolean needsAccountUpgrade ()
{
return mNeedsAccountUpgrade;
}
public void notifyAccountUpgrade ()
{
StatusBarNotifier notifier = new StatusBarNotifier(this);
}
public boolean doUpgrade (Activity activity, String newDomain, MigrateAccountTask.MigrateAccountListener listener)
{
boolean result = false;
long lastAccountId = settings.getLong("defaultAccountId",-1);
final Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT;
String[] PROVIDER_PROJECTION = {
Imps.Provider._ID,
Imps.Provider.ACTIVE_ACCOUNT_ID,
Imps.Provider.ACTIVE_ACCOUNT_USERNAME,
Imps.Provider.ACTIVE_ACCOUNT_NICKNAME,
Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN
};
final Cursor cursorProviders = getContentResolver().query(uri, PROVIDER_PROJECTION,
Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */,
new String[]{ImApp.IMPS_CATEGORY} /* selection args */,
Imps.Provider.DEFAULT_SORT_ORDER);
if (cursorProviders != null && cursorProviders.getCount() > 0) {
while(cursorProviders.moveToNext()) {
long providerId = cursorProviders.getLong(0);
long accountId = cursorProviders.getLong(1);
String username = cursorProviders.getString(2);
String nickname = cursorProviders.getString(3);
boolean keepSignedIn = cursorProviders.getInt(4) != 0;
Cursor pCursor = getContentResolver().query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(providerId)}, null);
Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap(
pCursor, getContentResolver(), providerId, false /* don't keep updated */, null /* no handler */);
username = username + '@' + settings.getDomain();
if (settings.getDomain().equalsIgnoreCase("dukgo.com") && keepSignedIn)
{
new MigrateAccountTask(activity, this, providerId, accountId, listener).execute(newDomain);
}
settings.close();
}
}
if (cursorProviders != null)
cursorProviders.close();
mNeedsAccountUpgrade = false;
return result;
}
}