Java tutorial
/* * Copyright (C) 2014 ChatWing * * 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 com.chatwingsdk.activities; import android.annotation.SuppressLint; import android.content.Intent; import android.content.OperationApplicationException; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarActivity; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ProgressBar; import android.widget.TextView; import com.chatwingsdk.ChatWing; import com.chatwingsdk.Constants; import com.chatwingsdk.R; import com.chatwingsdk.contentproviders.ChatWingContentProvider; import com.chatwingsdk.events.faye.ChannelSubscriptionChangedEvent; import com.chatwingsdk.events.faye.FayePublishEvent; import com.chatwingsdk.events.faye.MessageReceivedEvent; import com.chatwingsdk.events.faye.ServerConnectionChangedEvent; import com.chatwingsdk.events.internal.AllSyncsCompletedEvent; import com.chatwingsdk.events.internal.SyncCommunicationBoxEvent; import com.chatwingsdk.events.internal.TouchUserInfoEvent; import com.chatwingsdk.events.internal.UserUnauthenticatedEvent; import com.chatwingsdk.events.internal.ViewProfileEvent; import com.chatwingsdk.fragments.CategoriesFragment; import com.chatwingsdk.fragments.ChatMessagesFragment; import com.chatwingsdk.fragments.ChatboxesFragment; import com.chatwingsdk.fragments.ColorPickerDialogFragment; import com.chatwingsdk.fragments.CommunicationDrawerFragment; import com.chatwingsdk.fragments.CommunicationMessagesFragment; import com.chatwingsdk.fragments.ConversationMessagesFragment; import com.chatwingsdk.fragments.ConversationsFragment; import com.chatwingsdk.fragments.EmoticonsFragment; import com.chatwingsdk.fragments.InjectableFragmentDelegate; import com.chatwingsdk.fragments.NavigatableFragmentListener; import com.chatwingsdk.fragments.NewContentFragment; import com.chatwingsdk.fragments.ProfileFragment; import com.chatwingsdk.interfaces.ChatWingJSInterface; import com.chatwingsdk.managers.ApiManager; import com.chatwingsdk.managers.ChatboxModeManager; import com.chatwingsdk.managers.CommunicationModeManager; import com.chatwingsdk.managers.ConversationModeManager; import com.chatwingsdk.managers.SyncManager; import com.chatwingsdk.managers.UserManager; import com.chatwingsdk.modules.CommunicationActivityModule; import com.chatwingsdk.parsers.BBCodePair; import com.chatwingsdk.parsers.BBCodeParser; import com.chatwingsdk.parsers.EventParser; import com.chatwingsdk.pojos.Event; import com.chatwingsdk.pojos.Message; import com.chatwingsdk.pojos.jspojos.UserResponse; import com.chatwingsdk.pojos.params.CreateConversationParams; import com.chatwingsdk.services.CreateConversationIntentService; import com.chatwingsdk.services.SyncCommunicationBoxesIntentService; import com.chatwingsdk.utils.LogUtils; import com.chatwingsdk.views.BBCodeEditText; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import org.json.JSONException; import java.io.Serializable; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.inject.Inject; /** * This activity is the base where {@link com.chatwingsdk.fragments.CommunicationMessagesFragment} * and drawers are attached. Depend on which mode the activity is in, * appropriate {@link com.chatwingsdk.managers.CommunicationModeManager} is activated * <p/> * <p> * Due to otto limitation, subclasses must override * </p> * * @author cuongthai */ public class CommunicationActivity extends BaseABFragmentActivity implements NewContentFragment.Listener, ColorPickerDialogFragment.Listener, CommunicationMessagesFragment.Delegate, CommunicationModeManager.Delegate, InjectableFragmentDelegate, CommunicationDrawerFragment.Listener, NavigatableFragmentListener, ProfileFragment.Listener { private static final String EMOTICONS_FRAGMENT_TAG = "EmoticonsFragment"; private static final String ATTACHMENT_CONTENT_FRAGMENT_TAG = "NewContentFragment"; private static final int MODE_CHAT_BOX = 0; private static final int MODE_CONVERSATION = 1; public static final int REQUEST_CODE_AUTHENTICATION = 10000; private WebView mWebView; private boolean mNotSubscribeToChannels; @Inject ChatWingJSInterface mFayeJsInterface; @Inject protected ChatboxModeManager mChatboxModeManager; @Inject ConversationModeManager mConversationModeManager; @Inject Bus mBus; @Inject UserManager mUserManager; @Inject ApiManager mApiManager; @Inject EventParser mEventParser; @Inject SyncManager mSyncManager; protected CommunicationModeManager mCurrentCommunicationMode; private View mContentView; private View mProgressView; private ProgressBar mProgressBar; private TextView mProgressText; private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; // Flag to determined onCreate is called. It is used onResume to decide // whether a sync operation should be triggered or not and reset right // after that. private boolean mIsCreated; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ChatWing.instance(getApplicationContext()).getChatwingGraph().plus(); setContentView(R.layout.activity_communication); mBus.register(this); getSupportActionBar().setHomeButtonEnabled(true); mContentView = findViewById(R.id.fragment_container); mProgressView = findViewById(R.id.progress_container); mProgressBar = (ProgressBar) mProgressView.findViewById(R.id.loading_view); mProgressText = (TextView) mProgressView.findViewById(R.id.progress_text); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mChatboxModeManager.onCreate(savedInstanceState); mConversationModeManager.onCreate(savedInstanceState); int currentMode = getDefaultMode(); if (currentMode == MODE_CHAT_BOX) { setupChatboxMode(); } else { setupConversationMode(); } mIsCreated = true; } @Override protected void onStart() { super.onStart(); // Determine whether a sync categories operation should be // performed right now or not. // It should start if the activity is newly created (onCreate is called). // Thus, we don't refresh when the activity has been in background for a // short period of time. if (mIsCreated) { startSyncingCommunications(); mIsCreated = false; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); LogUtils.v("onActivityResult " + requestCode + ":" + resultCode); mCurrentCommunicationMode.onActivityResult(requestCode, resultCode, intent); if (requestCode == REQUEST_CODE_AUTHENTICATION && resultCode == RESULT_OK) { startSyncingCommunications(); } } @Override protected void onDestroy() { super.onDestroy(); LogUtils.v("On Destroy Activity"); mBus.unregister(this); if (mWebView != null) { mWebView.destroy(); mWebView = null; } mCurrentCommunicationMode.onDestroy(); mCurrentCommunicationMode = null; mNotSubscribeToChannels = true; } @Override protected void onResume() { super.onResume(); if (Build.VERSION.SDK_INT >= 11) { if (mWebView != null) { mWebView.onResume(); } } ensureWebViewAndSubscribeToChannels(); mChatboxModeManager.onResume(); mConversationModeManager.onResume(); invalidateOptionsMenu(); } @Override protected void onPause() { super.onPause(); if (mWebView != null) { if (Build.VERSION.SDK_INT >= 11) { mWebView.onPause(); } } mChatboxModeManager.onPause(); mConversationModeManager.onPause(); } @Override protected void onPostResume() { super.onPostResume(); mCurrentCommunicationMode.onPostResume(); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); } @Override public boolean onCreateOptionsMenu(Menu menu) { return mCurrentCommunicationMode.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { return mCurrentCommunicationMode.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { int i = item.getItemId(); if (i == android.R.id.home) { if (getDrawerLayout().isDrawerOpen(Gravity.LEFT)) { getDrawerLayout().closeDrawer(Gravity.LEFT); } else { getDrawerLayout().openDrawer(Gravity.LEFT); } return true; } else { return mCurrentCommunicationMode.onOptionsItemSelected(item); } } @Override protected List<Object> getModules() { return Arrays.<Object>asList(new CommunicationActivityModule(this)); } @Override public final void onItemClicked(NewContentFragment.Item item) { switch (item) { case BBCODES: getCommunicationMessagesFragment().showBBCodeControls(); break; case EMOTICONS: showEmoticonsFragment(); break; } } @Override public final void showColorPickerDialogFragment(BBCodeParser.BBCode code) { if (code == null) { throw new IllegalArgumentException("Code is required."); } ColorPickerDialogFragment fragment = ColorPickerDialogFragment.newInstance(code); fragment.show(getSupportFragmentManager(), code.toString()); } @Override public final void showNewContentFragment() { boolean hasBBCodes = !getCommunicationMessagesFragment().isShowingBBControls(); NewContentFragment fragment = NewContentFragment.newInstance(hasBBCodes); fragment.show(getSupportFragmentManager(), ATTACHMENT_CONTENT_FRAGMENT_TAG); } @Override public final void inject(BBCodeEditText mCommunicationBoxEditText) { super.inject(mCommunicationBoxEditText); } @Override public final void onConfirmColor(Serializable code, int color) { BBCodePair pair = new BBCodePair((BBCodeParser.BBCode) code, color); getCommunicationMessagesFragment().appendBBCode(pair); } @Override public final void inject(Fragment fragment) { super.inject(fragment); } @Override public final void handle(Exception exception, int errorMessageResId) { if (exception instanceof ApiManager.UserUnauthenticatedException) { onUserUnAuthenticatedEvent(null); } else { mErrorMessageView.show(exception, getString(R.string.error_unknown)); } } @Override public final ActionBarActivity getActivity() { return this; } @Override public final CommunicationMessagesFragment getCommunicationMessagesFragment() { String tag = getString(R.string.tag_communication_messages); return (CommunicationMessagesFragment) getSupportFragmentManager().findFragmentByTag(tag); } @Override public final void setProgressText(int resId, boolean showProgressBar) { setContentShown(false); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mProgressText.setText(resId); } @Override public final void setContentShown(boolean shown) { if (shown) { mContentView.setVisibility(View.VISIBLE); mProgressView.setVisibility(View.GONE); } else { mContentView.setVisibility(View.GONE); mProgressView.setVisibility(View.VISIBLE); } } @Override public final DrawerLayout getDrawerLayout() { return mDrawerLayout; } /** * Calls this in subclass to use it as Conversation Mode */ protected void setupConversationMode() { setupMode(mConversationModeManager, ConversationMessagesFragment.newInstance()); } /** * Calls this in subclass to use it as Public ChatBox Mode */ protected void setupChatboxMode() { setupMode(mChatboxModeManager, ChatMessagesFragment.newInstance()); } /** * Requires to be overriden by subclass to be called by otto * * @param event */ protected void onUserUnAuthenticatedEvent(UserUnauthenticatedEvent event) { mQuickMessageView.show(R.string.message_need_login); } @Override public void showCategories() { setTitle(getActivity().getString(R.string.title_chat_boxes)); invalidateOptionsMenu(); if (isInConversationMode()) { setupChatboxMode(); } addToLeftDrawer(getChatBoxesFragment()); } @Override public void showConversations() { setTitle(getActivity().getString(R.string.title_activity_conversation)); invalidateOptionsMenu(); if (isInChatBoxMode()) { setupConversationMode(); } addToLeftDrawer(new ConversationsFragment()); } @Override public void back(Fragment from) { getSupportFragmentManager().beginTransaction().setCustomAnimations(0, R.anim.slide_out_left).remove(from) .commit(); invalidateOptionsMenu(); } @Override public boolean onSwipe() { return startSyncingCommunications(); } @Override public WebView getWebView() { return mWebView; } @Override public void showConversation(CreateConversationParams.SimpleUser simpleUser) { initConversationMenu(); Intent createConversation = new Intent(getActivity(), CreateConversationIntentService.class); createConversation.putExtra(CreateConversationIntentService.EXTRA_USER, simpleUser); startService(createConversation); } @SuppressLint("SetJavaScriptEnabled") @Override public void ensureWebViewAndSubscribeToChannels() { if (mCurrentCommunicationMode == null) return; // Check whether the web view is available or not. // If not, init it and load faye client. When loading finished, // this method will be recursively called. At that point, // the actual subscribe code will be executed. if (mWebView == null) { mNotSubscribeToChannels = true; mWebView = new WebView(this); WebSettings webSettings = mWebView.getSettings(); webSettings.setJavaScriptEnabled(true); mWebView.addJavascriptInterface(mFayeJsInterface, ChatWingJSInterface.CHATWING_JS_NAME); mWebView.setWebChromeClient(new WebChromeClient() { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { LogUtils.v(consoleMessage.message() + " -- level " + consoleMessage.messageLevel() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId()); //This workaround tries to fix issue webview is not subscribe successfully //when the screen is off, we cant listen for otto event since it's dead before that //this likely happens on development or very rare case in production if (consoleMessage.messageLevel().equals(ConsoleMessage.MessageLevel.ERROR)) { mWebView = null; mNotSubscribeToChannels = true; } return true; } }); mWebView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (url.equals(Constants.FAYE_CLIENT_URL)) { // Recursively call this method, // to execute subscribe code. ensureWebViewAndSubscribeToChannels(); } } }); mWebView.loadUrl(Constants.FAYE_CLIENT_URL); return; } if (mNotSubscribeToChannels) { mNotSubscribeToChannels = false; mCurrentCommunicationMode.subscribeToChannels(mWebView); } } ////////////////////////////////////////// /// Otto ////////////////////////////////////////// @Subscribe public void onAllSyncsCompleted(AllSyncsCompletedEvent event) { mWebView = null; mNotSubscribeToChannels = true; ensureWebViewAndSubscribeToChannels(); LogUtils.v("Syncing All completed"); if (mCurrentCommunicationMode != null) { mCurrentCommunicationMode.reloadCurrentBox(); } } @Subscribe public void onTouchUserInfoEvent(TouchUserInfoEvent event) { UserResponse user = event.getUser(); String loginType = user.getLoginType(); String loginId = user.getLoginId(); String userAvatar = user.getUserAvatar(); String userProfileUrl = mApiManager.getUserProfileUrl(loginType, loginId); ProfileFragment newFragment = ProfileFragment.newInstance(new ViewProfileEvent(userProfileUrl, mApiManager.getAvatarUrl(loginType, loginId, userAvatar), user.getUserName(), loginType, loginId, mUserManager.getCurrentUser() == null ? true : user.equals(mUserManager.getCurrentUser()))); newFragment.show(getSupportFragmentManager(), "dialog"); } @Subscribe public void onChannelSubscriptionChanged(ChannelSubscriptionChangedEvent event) { //Faye if (event.getStatus() == ChannelSubscriptionChangedEvent.Status.SUCCEED) { if (Constants.DEBUG) { mQuickMessageView.show(getString(R.string.message_subscribed_to_channel) + event.getChannel()); } LogUtils.v("Subscribed to channel: " + event.getChannel()); mNotSubscribeToChannels = false; } else { // Failed mErrorMessageView.show(getString(R.string.message_error) + event.getError()); LogUtils.e(event.getError()); //We resubscribe next time mNotSubscribeToChannels = true; mWebView = null; } } @Subscribe public void onFayePublished(FayePublishEvent event) { //Faye if (event.getStatus() == FayePublishEvent.Status.SUCCEED) { if (Constants.DEBUG) { mQuickMessageView.show(R.string.message_published); } LogUtils.v("Published: " + event.toString()); } else { // Failed mErrorMessageView.show(getString(R.string.message_error) + event.getError()); LogUtils.e(event.getError()); } } @Subscribe public void onMessageReceived(MessageReceivedEvent event) { //Faye LogUtils.v("Java -----------"); LogUtils.v(System.currentTimeMillis() + ""); LogUtils.v("------------------"); LogUtils.v("Java main thread -----------"); LogUtils.v(System.currentTimeMillis() + ""); LogUtils.v("------------------"); try { Event e = mEventParser.parse(event.getMessage()); if (Constants.DEBUG) { mQuickMessageView.show(getString(R.string.message_event) + e.getName()); LogUtils.v("Event: " + e.getName()); } processEvent(e); } catch (JSONException ex) { handle(ex, R.string.message_error); } } @Subscribe public void onServerConnectionChangedEvent(ServerConnectionChangedEvent event) { //Faye if (event.getStatus() == ServerConnectionChangedEvent.Status.CONNECTED) { if (Constants.DEBUG) { mQuickMessageView.show(R.string.message_connected_to_server); } LogUtils.v("Connected to server."); } else { // Disconnected if (Constants.DEBUG) { mQuickMessageView.show(R.string.message_disconnected_from_server); } LogUtils.v("Disconnected from server."); } } @Subscribe public void onSyncCommunicationBoxEvent(SyncCommunicationBoxEvent event) { SyncCommunicationBoxEvent.Status status = event.getStatus(); switch (status) { case STARTED: break; case SUCCEED: /* * TODO when succeed, may need to check if the current chat * box is still valid. * And un-subscribe to all invalid chat boxes. */ ensureWebViewAndSubscribeToChannels(); break; case FAILED: handle(event.getException(), R.string.error_failed_to_sync_data); break; } } private void processEvent(Event event) { String name = event.getName(); if (name.equals(EventParser.EVENT_NEW_MESSAGE) || name.equals(EventParser.EVENT_NETWORK_NEW_MESSAGE)) { Message message = (Message) event.getParams(); message.setStatus(Message.Status.PUBLISHED); boolean isInCurrentCommunicationBox = mCurrentCommunicationMode.isInCurrentCommunicationBox(message); if (!isInCurrentCommunicationBox) { mCurrentCommunicationMode.processMessageNotInCurrentCommunicationBox(message); } } } ////////////////////////// // Instance methods ////////////////////////// private void initConversationMenu() { setTitle(getActivity().getString(R.string.title_activity_conversation)); if (isInChatBoxMode()) { setupConversationMode(); } invalidateOptionsMenu(); } private boolean isInChatBoxMode() { return mCurrentCommunicationMode == null || mCurrentCommunicationMode instanceof ChatboxModeManager; } private boolean isInConversationMode() { return mCurrentCommunicationMode == null || mCurrentCommunicationMode instanceof ConversationModeManager; } private void addToLeftDrawer(Fragment fragment) { addToLeftDrawer(fragment, true); } private void addToLeftDrawer(Fragment fragment, boolean animate) { if (animate) { getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_right, R.anim.slide_out_left) .replace(R.id.left_drawer_container, fragment).addToBackStack(null).commit(); } else { getSupportFragmentManager().beginTransaction().replace(R.id.left_drawer_container, fragment) .addToBackStack(null).commit(); } } private Fragment getChatBoxesFragment() { return new CategoriesFragment(); } protected void setupMode(CommunicationModeManager newMode, CommunicationMessagesFragment newFragment) { /* * Update fragments */ String fragmentTag = getString(R.string.tag_communication_messages); FragmentManager fragmentManager = getSupportFragmentManager(); Fragment oldFragment = fragmentManager.findFragmentByTag(fragmentTag); FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); if (oldFragment != null) { fragmentTransaction.remove(oldFragment); } fragmentTransaction.add(R.id.fragment_container, newFragment, fragmentTag); fragmentTransaction.commit(); /* * Deactivate old mode and activate the new one */ if (mCurrentCommunicationMode != null) { mCurrentCommunicationMode.deactivate(); } mCurrentCommunicationMode = newMode; mCurrentCommunicationMode.activate(); /* * Setup drawer layout */ // Set custom shadows that overlay the main content when a drawer opens mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow_left, Gravity.LEFT); mDrawerToggle = mCurrentCommunicationMode.getDrawerToggleListener(); mDrawerToggle.setDrawerIndicatorEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); mDrawerLayout.setDrawerListener(mDrawerToggle); // Don't allow the drawer layout to catch back button and close itself // on back key is pressed. This activity will handle it. mDrawerLayout.setFocusableInTouchMode(false); invalidateOptionsMenu(); } private void showEmoticonsFragment() { Map<String, String> emoticons = mCurrentCommunicationMode.getEmoticons(); TreeMap<String, String> treeMap; if (emoticons instanceof TreeMap) { //noinspection unchecked treeMap = (TreeMap) emoticons; } else { treeMap = new TreeMap<String, String>(emoticons); } EmoticonsFragment fragment = EmoticonsFragment.newInstance(treeMap); fragment.show(getSupportFragmentManager(), EMOTICONS_FRAGMENT_TAG); } @Override public void logout() { if (mUserManager.getCurrentUser() == null) { return; } mCurrentCommunicationMode.logout(); mUserManager.removeUsers(); try { getContentResolver().applyBatch(ChatWingContentProvider.AUTHORITY, ChatWingContentProvider.getClearAllDataBatch()); } catch (RemoteException e) { LogUtils.e(e); } catch (OperationApplicationException e) { LogUtils.e(e); } Intent i = new Intent(this, getEntranceActivityClass()); startActivity(i); finish(); } protected Class<? extends BaseABFragmentActivity> getEntranceActivityClass() { return CommunicationActivity.class; } private boolean startSyncingCommunications() { if (SyncCommunicationBoxesIntentService.isInProgress()) { // A sync operation is running. Just wait for it. return false; } mSyncManager.resetQueue(); mSyncManager.addToQueue(SyncCommunicationBoxesIntentService.class); Intent intent = new Intent(this, SyncCommunicationBoxesIntentService.class); startService(intent); return true; } private int getDefaultMode() { return MODE_CHAT_BOX; } }