/** * Copyright (c) 2013, Redsolution LTD. All rights reserved. * * This file is part of Xabber project; you can redistribute it and/or * modify it under the terms of the GNU General Public License, Version 3. * * Xabber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License, * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.xabber.android.data; import android.app.Activity; import android.os.Handler; import android.support.annotation.NonNull; import com.frogermcs.androiddevmetrics.AndroidDevMetrics; import com.xabber.android.BuildConfig; import com.xabber.android.R; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.account.ScreenManager; import com.xabber.android.data.connection.CertificateManager; import com.xabber.android.data.connection.ConnectionManager; import com.xabber.android.data.connection.NetworkManager; import com.xabber.android.data.connection.ReconnectionManager; import com.xabber.android.data.database.DatabaseManager; import com.xabber.android.data.extension.attention.AttentionManager; import com.xabber.android.data.extension.avatar.AvatarManager; import com.xabber.android.data.extension.avatar.AvatarStorage; import com.xabber.android.data.extension.blocking.BlockingManager; import com.xabber.android.data.extension.capability.CapabilitiesManager; import com.xabber.android.data.extension.carbons.CarbonManager; import com.xabber.android.data.extension.cs.ChatStateManager; import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; import com.xabber.android.data.extension.mam.MamManager; import com.xabber.android.data.extension.muc.MUCManager; import com.xabber.android.data.extension.otr.OTRManager; import com.xabber.android.data.extension.ssn.SSNManager; import com.xabber.android.data.extension.vcard.VCardManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.message.MessageManager; import com.xabber.android.data.message.ReceiptManager; import com.xabber.android.data.message.chat.ChatManager; import com.xabber.android.data.message.phrase.PhraseManager; import com.xabber.android.data.notification.NotificationManager; import com.xabber.android.data.roster.GroupManager; import com.xabber.android.data.roster.PresenceManager; import com.xabber.android.data.roster.RosterManager; import com.xabber.android.service.XabberService; import org.jivesoftware.smack.provider.ProviderFileLoader; import org.jivesoftware.smack.provider.ProviderManager; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; /** * Base entry point. * * @author alexander.ivanov */ public class Application extends android.app.Application { private static final String LOG_TAG = Application.class.getSimpleName(); private static Application instance; private final ArrayList<Object> registeredManagers; /** * Thread to execute tasks in background.. */ private final ExecutorService backgroundExecutor; private final ExecutorService backgroundExecutorForUserActions; /** * Handler to execute runnable in UI thread. */ private final Handler handler; /** * Unmodifiable collections of managers that implement some common * interface. */ private Map<Class<? extends BaseManagerInterface>, Collection<? extends BaseManagerInterface>> managerInterfaces; private Map<Class<? extends BaseUIListener>, Collection<? extends BaseUIListener>> uiListeners; /** * Where data load was requested. */ private boolean serviceStarted; /** * Whether application was initialized. */ private boolean initialized; /** * Whether user was notified about some action in contact list activity * after application initialization. */ private boolean notified; /** * Whether application is to be closed. */ private boolean closing; /** * Whether {@link #onServiceDestroy()} has been called. */ private boolean closed; private final Runnable timerRunnable = new Runnable() { @Override public void run() { for (OnTimerListener listener : getManagers(OnTimerListener.class)) { listener.onTimer(); } if (!closing) { startTimer(); } } }; /** * Future for loading process. */ private Future<Void> loadFuture; public Application() { instance = this; serviceStarted = false; initialized = false; notified = false; closing = false; closed = false; uiListeners = new HashMap<>(); managerInterfaces = new HashMap<>(); registeredManagers = new ArrayList<>(); handler = new Handler(); backgroundExecutor = createSingleThreadExecutor("Background executor service"); backgroundExecutorForUserActions = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors(), new ThreadFactory() { @Override public Thread newThread(@NonNull Runnable runnable) { Thread thread = new Thread(runnable); thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); return thread; } }); } @NonNull private ExecutorService createSingleThreadExecutor(final String threadName) { return Executors.newSingleThreadExecutor(new ThreadFactory() { @Override public Thread newThread(@NonNull Runnable runnable) { Thread thread = new Thread(runnable, threadName); thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); return thread; } }); } public static Application getInstance() { if (instance == null) { throw new IllegalStateException(); } return instance; } /** * Whether application is initialized. */ public boolean isInitialized() { return initialized; } private void onLoad() { ProviderManager.addLoader(new ProviderFileLoader(getResources().openRawResource(R.raw.smack))); for (OnLoadListener listener : getManagers(OnLoadListener.class)) { LogManager.i(listener, "onLoad"); listener.onLoad(); } } private void onInitialized() { for (OnInitializedListener listener : getManagers(OnInitializedListener.class)) { LogManager.i(listener, "onInitialized"); listener.onInitialized(); } initialized = true; XabberService.getInstance().changeForeground(); startTimer(); } private void onClose() { LogManager.i(LOG_TAG, "onClose1"); for (Object manager : registeredManagers) { if (manager instanceof OnCloseListener) { ((OnCloseListener) manager).onClose(); } } closed = true; LogManager.i(LOG_TAG, "onClose2"); } void onUnload() { LogManager.i(LOG_TAG, "onUnload1"); for (Object manager : registeredManagers) { if (manager instanceof OnUnloadListener) { ((OnUnloadListener) manager).onUnload(); } } LogManager.i(LOG_TAG, "onUnload2"); android.os.Process.killProcess(android.os.Process.myPid()); } /** * @return <code>true</code> only once per application life. Subsequent * calls will always returns <code>false</code>. */ public boolean doNotify() { if (notified) { return false; } notified = true; return true; } /** * Starts data loading in background if not started yet. */ public void onServiceStarted() { if (serviceStarted) { return; } serviceStarted = true; LogManager.i(this, "onStart"); loadFuture = backgroundExecutor.submit(new Callable<Void>() { @Override public Void call() throws Exception { try { onLoad(); } finally { runOnUiThread(new Runnable() { @Override public void run() { // Throw exceptions in UI thread if any. try { loadFuture.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } onInitialized(); } }); } return null; } }); } /** * Requests to close application in some time in future. */ public void requestToClose() { LogManager.i(LOG_TAG, "requestToClose1"); closing = true; stopService(XabberService.createIntent(this)); LogManager.i(LOG_TAG, "requestToClose2"); } /** * @return Whether application is to be closed. */ public boolean isClosing() { return closing; } @Override public void onCreate() { super.onCreate(); if (BuildConfig.DEBUG) { AndroidDevMetrics.initWith(this); } Thread.currentThread().setPriority(Thread.MAX_PRIORITY); addManagers(); DatabaseManager.getInstance().addTables(); LogManager.i(this, "onCreate finished..."); } private void addManagers() { addManager(SettingsManager.getInstance()); addManager(LogManager.getInstance()); addManager(DatabaseManager.getInstance()); addManager(AvatarStorage.getInstance()); addManager(OTRManager.getInstance()); addManager(ConnectionManager.getInstance()); addManager(ScreenManager.getInstance()); addManager(AccountManager.getInstance()); addManager(MUCManager.getInstance()); addManager(MessageManager.getInstance()); addManager(ChatManager.getInstance()); addManager(VCardManager.getInstance()); addManager(AvatarManager.getInstance()); addManager(PresenceManager.getInstance()); addManager(RosterManager.getInstance()); addManager(GroupManager.getInstance()); addManager(PhraseManager.getInstance()); addManager(NotificationManager.getInstance()); addManager(ActivityManager.getInstance()); addManager(CapabilitiesManager.getInstance()); addManager(ChatStateManager.getInstance()); addManager(NetworkManager.getInstance()); addManager(ReconnectionManager.getInstance()); addManager(ReceiptManager.getInstance()); addManager(SSNManager.getInstance()); addManager(AttentionManager.getInstance()); addManager(CarbonManager.getInstance()); addManager(HttpFileUploadManager.getInstance()); addManager(BlockingManager.getInstance()); addManager(MamManager.getInstance()); addManager(CertificateManager.getInstance()); } /** * Register new manager. */ private void addManager(Object manager) { registeredManagers.add(manager); } @Override public void onLowMemory() { for (OnLowMemoryListener listener : getManagers(OnLowMemoryListener.class)) { listener.onLowMemory(); } super.onLowMemory(); } /** * Service have been destroyed. */ public void onServiceDestroy() { LogManager.i(LOG_TAG, "onServiceDestroy"); if (closed) { LogManager.i(LOG_TAG, "onServiceDestroy closed"); return; } onClose(); // use new thread instead of run in background to exit immediately // without waiting for possible other threads in executor Thread thread = new Thread(new Runnable() { @Override public void run() { onUnload(); } }); thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); thread.start(); } @Override public void onTerminate() { requestToClose(); super.onTerminate(); } /** * Start periodically callbacks. */ private void startTimer() { runOnUiThreadDelay(timerRunnable, OnTimerListener.DELAY); } /** * @param cls Requested class of managers. * @return List of registered manager. */ @SuppressWarnings("unchecked") public <T extends BaseManagerInterface> Collection<T> getManagers(Class<T> cls) { if (closed) { return Collections.emptyList(); } Collection<T> collection = (Collection<T>) managerInterfaces.get(cls); if (collection == null) { collection = new ArrayList<>(); for (Object manager : registeredManagers) { if (cls.isInstance(manager)) { collection.add((T) manager); } } collection = Collections.unmodifiableCollection(collection); managerInterfaces.put(cls, collection); } return collection; } /** * Request to clear application data. */ public void requestToClear() { runInBackground(new Runnable() { @Override public void run() { clear(); } }); } private void clear() { for (Object manager : registeredManagers) { if (manager instanceof OnClearListener) { ((OnClearListener) manager).onClear(); } } } /** * Request to wipe all sensitive application data. */ public void requestToWipe() { runInBackground(new Runnable() { @Override public void run() { clear(); for (Object manager : registeredManagers) if (manager instanceof OnWipeListener) ((OnWipeListener) manager).onWipe(); } }); } @SuppressWarnings("unchecked") private <T extends BaseUIListener> Collection<T> getOrCreateUIListeners(Class<T> cls) { Collection<T> collection = (Collection<T>) uiListeners.get(cls); if (collection == null) { collection = new ArrayList<T>(); uiListeners.put(cls, collection); } return collection; } /** * @param cls Requested class of listeners. * @return List of registered UI listeners. */ public <T extends BaseUIListener> Collection<T> getUIListeners(Class<T> cls) { if (closed) { return Collections.emptyList(); } return Collections.unmodifiableCollection(getOrCreateUIListeners(cls)); } /** * Register new listener. * <p/> * Should be called from {@link Activity#onResume()}. */ public <T extends BaseUIListener> void addUIListener(Class<T> cls, T listener) { getOrCreateUIListeners(cls).add(listener); } /** * Unregister listener. * <p/> * Should be called from {@link Activity#onPause()}. */ public <T extends BaseUIListener> void removeUIListener(Class<T> cls, T listener) { getOrCreateUIListeners(cls).remove(listener); } /** * Notify about error. */ public void onError(final int resourceId) { runOnUiThread(new Runnable() { @Override public void run() { for (OnErrorListener onErrorListener : getUIListeners(OnErrorListener.class)) { onErrorListener.onError(resourceId); } } }); } /** * Notify about error. */ public void onError(NetworkException networkException) { LogManager.exception(this, networkException); onError(networkException.getResourceId()); } /** * Submits request to be executed in background. */ public void runInBackground(final Runnable runnable) { backgroundExecutor.submit(new Runnable() { @Override public void run() { try { runnable.run(); } catch (Exception e) { LogManager.exception(runnable, e); } } }); } public void runInBackgroundUserRequest(final Runnable runnable) { backgroundExecutorForUserActions.submit(new Runnable() { @Override public void run() { try { runnable.run(); } catch (Exception e) { LogManager.exception(runnable, e); } } }); } /** * Submits request to be executed in UI thread. */ public void runOnUiThread(final Runnable runnable) { handler.post(runnable); } /** * Submits request to be executed in UI thread. */ public void runOnUiThreadDelay(final Runnable runnable, long delayMillis) { handler.postDelayed(runnable, delayMillis); } }