Java tutorial
/* * Copyright (C) 2008 Esmertec AG. Copyright (C) 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.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Resources; import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.Cursor; import android.database.CursorIndexOutOfBoundsException; import android.database.DataSetObserver; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.provider.Browser; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.view.ViewPager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.TextWatcher; import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.util.Log; import android.view.ActionMode; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.TextureView; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import net.java.otr4j.session.SessionStatus; import org.awesomeapp.messenger.ImApp; import org.awesomeapp.messenger.Preferences; import org.awesomeapp.messenger.bho.DictionarySearch; import org.awesomeapp.messenger.crypto.IOtrChatSession; import org.awesomeapp.messenger.crypto.OtrAndroidKeyManagerImpl; import org.awesomeapp.messenger.crypto.OtrChatManager; import org.awesomeapp.messenger.model.Address; import org.awesomeapp.messenger.model.Contact; import org.awesomeapp.messenger.model.ImConnection; import org.awesomeapp.messenger.model.ImErrorInfo; import org.awesomeapp.messenger.model.Presence; import org.awesomeapp.messenger.plugin.xmpp.XmppAddress; import org.awesomeapp.messenger.provider.Imps; import org.awesomeapp.messenger.provider.ImpsAddressUtils; import org.awesomeapp.messenger.service.IChatListener; import org.awesomeapp.messenger.service.IChatSession; import org.awesomeapp.messenger.service.IChatSessionManager; import org.awesomeapp.messenger.service.IContactList; import org.awesomeapp.messenger.service.IContactListListener; import org.awesomeapp.messenger.service.IContactListManager; import org.awesomeapp.messenger.service.IImConnection; import org.awesomeapp.messenger.service.ImServiceConstants; import org.awesomeapp.messenger.tasks.ChatSessionInitTask; import org.awesomeapp.messenger.ui.MessageListItem.DeliveryState; import org.awesomeapp.messenger.ui.MessageListItem.EncryptionState; import org.awesomeapp.messenger.ui.legacy.DatabaseUtils; import org.awesomeapp.messenger.ui.legacy.Markup; import org.awesomeapp.messenger.ui.legacy.SimpleAlertHandler; import org.awesomeapp.messenger.ui.legacy.adapter.ChatListenerAdapter; import org.awesomeapp.messenger.ui.stickers.Sticker; import org.awesomeapp.messenger.ui.stickers.StickerGroup; import org.awesomeapp.messenger.ui.stickers.StickerManager; import org.awesomeapp.messenger.ui.stickers.StickerPagerAdapter; import org.awesomeapp.messenger.ui.stickers.StickerSelectListener; import org.awesomeapp.messenger.ui.widgets.MessageViewHolder; import org.awesomeapp.messenger.ui.widgets.RoundedAvatarDrawable; import org.awesomeapp.messenger.util.Debug; import org.awesomeapp.messenger.util.GiphyAPI; import org.awesomeapp.messenger.util.LogCleaner; import org.awesomeapp.messenger.util.SystemServices; import org.ironrabbit.type.CustomTypefaceManager; import org.ironrabbit.type.CustomTypefaceSpan; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import im.zom.messenger.R; public class ConversationView { // This projection and index are set for the query of active chats public static final String[] CHAT_PROJECTION = { Imps.Contacts._ID, Imps.Contacts.ACCOUNT, Imps.Contacts.PROVIDER, Imps.Contacts.USERNAME, Imps.Contacts.NICKNAME, Imps.Contacts.TYPE, Imps.Presence.PRESENCE_STATUS, Imps.Chats.LAST_UNREAD_MESSAGE, Imps.Chats._ID, Imps.Contacts.SUBSCRIPTION_TYPE, Imps.Contacts.SUBSCRIPTION_STATUS, Imps.Contacts.AVATAR_DATA }; public static final int CONTACT_ID_COLUMN = 0; public static final int ACCOUNT_COLUMN = 1; public static final int PROVIDER_COLUMN = 2; public static final int USERNAME_COLUMN = 3; public static final int NICKNAME_COLUMN = 4; public static final int TYPE_COLUMN = 5; public static final int PRESENCE_STATUS_COLUMN = 6; public static final int LAST_UNREAD_MESSAGE_COLUMN = 7; public static final int CHAT_ID_COLUMN = 8; public static final int SUBSCRIPTION_TYPE_COLUMN = 9; public static final int SUBSCRIPTION_STATUS_COLUMN = 10; public static final int AVATAR_COLUMN = 11; //static final int MIME_TYPE_COLUMN = 9; static final String[] INVITATION_PROJECT = { Imps.Invitation._ID, Imps.Invitation.PROVIDER, Imps.Invitation.SENDER, }; static final int INVITATION_ID_COLUMN = 0; static final int INVITATION_PROVIDER_COLUMN = 1; static final int INVITATION_SENDER_COLUMN = 2; static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD); static final StyleSpan STYLE_NORMAL = new StyleSpan(Typeface.NORMAL); Markup mMarkup; ConversationDetailActivity mActivity; ImApp mApp; private SimpleAlertHandler mHandler; IImConnection mConn; //private ImageView mStatusIcon; // private TextView mTitle; /*package*/RecyclerView mHistory; EditText mComposeMessage; private ImageButton mSendButton, mMicButton; private TextView mButtonTalk; private ImageButton mButtonAttach; private View mViewAttach; private ImageView mButtonDeleteVoice; private View mViewDeleteVoice; private View mStatusWarningView; private TextView mWarningText; private ImageView mDeliveryIcon; private boolean mExpectingDelivery; private boolean mIsSelected = false; private SessionStatus mLastSessionStatus = null; private boolean mIsStartingOtr = false; private boolean mIsVerified = false; private ConversationRecyclerViewAdapter mMessageAdapter; private boolean isServiceUp; private IChatSession mCurrentChatSession; long mLastChatId = -1; String mRemoteNickname; String mRemoteAddress; RoundedAvatarDrawable mRemoteAvatar = null; Drawable mRemoteHeader = null; int mSubscriptionType; int mSubscriptionStatus; long mProviderId = -1; long mAccountId = -1; long mInvitationId; private Context mContext; // TODO private int mPresenceStatus; private Date mLastSeen; private int mViewType; private static final int VIEW_TYPE_CHAT = 1; private static final int VIEW_TYPE_INVITATION = 2; private static final int VIEW_TYPE_SUBSCRIPTION = 3; // private static final long SHOW_TIME_STAMP_INTERVAL = 30 * 1000; // 15 seconds private static final long SHOW_DELIVERY_INTERVAL = 10 * 1000; // 5 seconds private static final long SHOW_MEDIA_DELIVERY_INTERVAL = 120 * 1000; // 2 minutes private static final long DEFAULT_QUERY_INTERVAL = 2000; private static final long FAST_QUERY_INTERVAL = 200; private RequeryCallback mRequeryCallback = null; public SimpleAlertHandler getHandler() { return mHandler; } public int getType() { return mViewType; } private class RequeryCallback implements Runnable { public void run() { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("RequeryCallback"); } requeryCursor(); } } public void setSelected(boolean isSelected) { mIsSelected = isSelected; if (mIsSelected) { // bindChat(mLastChatId); startListening(); updateWarningView(); mComposeMessage.requestFocus(); userActionDetected(); try { IContactListManager manager = mConn.getContactListManager(); Contact contact = manager.getContactByAddress(mRemoteAddress); if (contact != null && contact.getPresence() != null) { if (contact.getPresence().getStatus() == Presence.AVAILABLE) { mLastSeen = contact.getPresence().getLastSeen(); mActivity.updateLastSeen(mLastSeen); } } if ((mLastSessionStatus == null || mLastSessionStatus == SessionStatus.PLAINTEXT)) { //boolean otrPolicyAuto = mActivity.getOtrPolicy() == OtrPolicy.OTRL_POLICY_ALWAYS //|| this.mNewChatActivity.getOtrPolicy() == OtrPolicy.OPPORTUNISTIC; boolean otrPolicyAuto = true; if (mCurrentChatSession == null) mCurrentChatSession = getChatSession(); if (mCurrentChatSession == null) return; IOtrChatSession otrChatSession = mCurrentChatSession.getDefaultOtrChatSession(); if (otrChatSession != null && (!isGroupChat())) { String remoteJID = otrChatSession.getRemoteUserId(); boolean doOtr = (remoteJID != null && (remoteJID.toLowerCase().contains("chatsecure") || remoteJID.toLowerCase().contains("zom"))); if (!doOtr) doOtr = OtrAndroidKeyManagerImpl.getInstance(mActivity).hasRemoteFingerprint(remoteJID); if (otrPolicyAuto && doOtr) //if set to auto, and is chatsecure, then start encryption { //automatically attempt to turn on OTR after 1 second mHandler.postDelayed(new Runnable() { public void run() { setOTRState(true); scheduleRequery(DEFAULT_QUERY_INTERVAL); } }, 100); } } } } catch (RemoteException re) { } } else { stopListening(); sendTypingStatus(false); } } public void inviteContacts(ArrayList<String> invitees) { if (mConn == null) return; try { IChatSessionManager manager = mConn.getChatSessionManager(); IChatSession session = manager.getChatSession(mRemoteAddress); for (String invitee : invitees) session.inviteContact(invitee); } catch (Exception e) { Log.e(ImApp.LOG_TAG, "error inviting contacts to group", e); } } // collapsingToolbar.setCollapsedTitleTypeface(typeface); // collapsingToolbar.setExpandedTitleTypeface(typeface); private boolean checkConnection() { if (mConn == null && mProviderId != -1) { mConn = mApp.getConnection(mProviderId, mAccountId); if (mConn == null) return false; } return true; } public void setOTRState(boolean otrEnabled) { try { if (mCurrentChatSession == null) mCurrentChatSession = getChatSession(); if (mCurrentChatSession != null) { IOtrChatSession otrChatSession = mCurrentChatSession.getDefaultOtrChatSession(); if (otrChatSession != null) { if (otrEnabled && (otrChatSession.getChatStatus() != SessionStatus.ENCRYPTED.ordinal())) { otrChatSession.startChatEncryption(); mIsStartingOtr = true; } // else if ((!otrEnabled) && otrChatSession.getChatStatus() == SessionStatus.ENCRYPTED.ordinal()) else { otrChatSession.stopChatEncryption(); } } } updateWarningView(); } catch (RemoteException e) { Log.d(ImApp.LOG_TAG, "error getting remote activity", e); } } private OnItemClickListener mOnItemClickListener = new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (!(view instanceof MessageListItem)) { return; } URLSpan[] links = ((MessageListItem) view).getMessageLinks(); if (links.length > 0) { final ArrayList<String> linkUrls = new ArrayList<String>(links.length); for (URLSpan u : links) { linkUrls.add(u.getURL()); } ArrayAdapter<String> a = new ArrayAdapter<String>(mActivity, android.R.layout.select_dialog_item, linkUrls); AlertDialog.Builder b = new AlertDialog.Builder(mActivity); b.setTitle(R.string.select_link_title); b.setCancelable(true); b.setAdapter(a, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Uri uri = Uri.parse(linkUrls.get(which)); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName()); mActivity.startActivity(intent); } }); b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); b.show(); } } }; private final static int PROMPT_FOR_DATA_TRANSFER = 9999; private final static int SHOW_DATA_PROGRESS = 9998; private final static int SHOW_DATA_ERROR = 9997; private final static int SHOW_TYPING = 9996; private IChatListener mChatListener = new ChatListenerAdapter() { @Override public boolean onIncomingMessage(IChatSession ses, org.awesomeapp.messenger.model.Message msg) { return mIsSelected; } @Override public void onContactJoined(IChatSession ses, Contact contact) { } @Override public void onContactLeft(IChatSession ses, Contact contact) { } @Override public void onSendMessageError(IChatSession ses, org.awesomeapp.messenger.model.Message msg, ImErrorInfo error) { } @Override public void onIncomingReceipt(IChatSession ses, String packetId) throws RemoteException { scheduleRequery(DEFAULT_QUERY_INTERVAL); } @Override public void onStatusChanged(IChatSession ses) throws RemoteException { scheduleRequery(DEFAULT_QUERY_INTERVAL); updatePresenceDisplay(); } @Override public void onIncomingFileTransfer(String transferFrom, String transferUrl) throws RemoteException { String[] path = transferUrl.split("/"); String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); android.os.Message message = android.os.Message.obtain(null, PROMPT_FOR_DATA_TRANSFER, (int) (mProviderId >> 32), (int) mProviderId, -1); message.getData().putString("from", transferFrom); message.getData().putString("file", sanitizedPath); mHandler.sendMessage(message); log("onIncomingFileTransfer: " + transferFrom + " @ " + transferUrl); } @Override public void onIncomingFileTransferProgress(String file, int percent) throws RemoteException { if (percent == 100) scheduleRequery(FAST_QUERY_INTERVAL); /** android.os.Message message = android.os.Message.obtain(null, SHOW_DATA_PROGRESS, (int) (mProviderId >> 32), (int) mProviderId, -1); message.getData().putString("file", file); message.getData().putInt("progress", percent); mHandler.sendMessage(message);*/ log("onIncomingFileTransferProgress: " + file + " " + percent + "%"); } @Override public void onIncomingFileTransferError(String file, String err) throws RemoteException { android.os.Message message = android.os.Message.obtain(null, SHOW_DATA_ERROR, (int) (mProviderId >> 32), (int) mProviderId, -1); message.getData().putString("file", file); message.getData().putString("err", err); mHandler.sendMessage(message); log("onIncomingFileTransferProgress: " + file + " err: " + err); } @Override public void onContactTyping(IChatSession ses, Contact contact, boolean isTyping) throws RemoteException { super.onContactTyping(ses, contact, isTyping); if (contact.getPresence() != null) { mPresenceStatus = contact.getPresence().getStatus(); mLastSeen = contact.getPresence().getLastSeen(); } else { mLastSeen = new Date(); } mActivity.updateLastSeen(mLastSeen); android.os.Message message = android.os.Message.obtain(null, SHOW_TYPING, (int) (mProviderId >> 32), (int) mProviderId, -1); message.getData().putBoolean("typing", isTyping); mHandler.sendMessage(message); } }; private void showPromptForData(final String transferFrom, String filePath) { AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); builder.setTitle(mContext.getString(R.string.file_transfer)); builder.setMessage(transferFrom + ' ' + mActivity.getString(R.string.wants_to_send_you_the_file) + " '" + filePath + "'. " + mActivity.getString(R.string.accept_transfer_)); builder.setNeutralButton(R.string.button_yes_accept_all, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { try { mCurrentChatSession.setIncomingFileResponse(transferFrom, true, true); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } dialog.dismiss(); } }); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { try { mCurrentChatSession.setIncomingFileResponse(transferFrom, true, false); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } dialog.dismiss(); } }); builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { mCurrentChatSession.setIncomingFileResponse(transferFrom, false, false); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Do nothing dialog.dismiss(); } }); AlertDialog alert = builder.create(); alert.show(); } private Runnable mUpdateChatCallback = new Runnable() { public void run() { // if (mCursor != null && mCursor.requery() && mCursor.moveToFirst()) { updateChat(); // } } }; private IContactListListener mContactListListener = new IContactListListener.Stub() { public void onAllContactListsLoaded() { } public void onContactChange(int type, IContactList list, Contact contact) { if (contact != null && contact.getPresence() != null) { mPresenceStatus = contact.getPresence().getStatus(); } mLastSeen = new Date(); mActivity.updateLastSeen(mLastSeen); } public void onContactError(int errorType, ImErrorInfo error, String listName, Contact contact) { } public void onContactsPresenceUpdate(Contact[] contacts) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("onContactsPresenceUpdate()"); } for (Contact c : contacts) { if (c.getAddress().getBareAddress().equals(Address.stripResource(mRemoteAddress))) { if (c != null && c.getPresence() != null) { mPresenceStatus = c.getPresence().getStatus(); if (mPresenceStatus == Presence.AVAILABLE) { mLastSeen = c.getPresence().getLastSeen(); mActivity.updateLastSeen(mLastSeen); } updatePresenceDisplay(); } mHandler.post(mUpdateChatCallback); scheduleRequery(DEFAULT_QUERY_INTERVAL); break; } } } }; private boolean mIsListening; static final void log(String msg) { if (Debug.DEBUG_ENABLED) Log.d(ImApp.LOG_TAG, "<ChatView> " + msg); } public ConversationView(ConversationDetailActivity activity) { mActivity = activity; mContext = activity; mApp = (ImApp) mActivity.getApplication(); mHandler = new ChatViewHandler(mActivity); initViews(); } void registerForConnEvents() { mApp.registerForConnEvents(mHandler); } void unregisterForConnEvents() { mApp.unregisterForConnEvents(mHandler); } protected void initViews() { // mStatusIcon = (ImageView) mActivity.findViewById(R.id.statusIcon); // mDeliveryIcon = (ImageView) mActivity.findViewById(R.id.deliveryIcon); // mTitle = (TextView) mActivity.findViewById(R.id.title); mHistory = (RecyclerView) mActivity.findViewById(R.id.history); LinearLayoutManager llm = new LinearLayoutManager(mHistory.getContext()); llm.setStackFromEnd(true); mHistory.setLayoutManager(llm); mComposeMessage = (EditText) mActivity.findViewById(R.id.composeMessage); mSendButton = (ImageButton) mActivity.findViewById(R.id.btnSend); mMicButton = (ImageButton) mActivity.findViewById(R.id.btnMic); mButtonTalk = (TextView) mActivity.findViewById(R.id.buttonHoldToTalk); mButtonDeleteVoice = (ImageView) mActivity.findViewById(R.id.btnDeleteVoice); mViewDeleteVoice = mActivity.findViewById(R.id.viewDeleteVoice); mButtonDeleteVoice.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) { int resolvedColor = mHistory.getResources().getColor(android.R.color.holo_red_light); mButtonDeleteVoice.setBackgroundColor(resolvedColor); } return false; } }); mButtonAttach = (ImageButton) mActivity.findViewById(R.id.btnAttach); mViewAttach = mActivity.findViewById(R.id.attachPanel); mStatusWarningView = mActivity.findViewById(R.id.warning); mWarningText = (TextView) mActivity.findViewById(R.id.warningText); mButtonAttach.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toggleAttachMenu(); } }); mActivity.findViewById(R.id.btnAttachPicture).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mActivity.startImagePicker(); } }); mActivity.findViewById(R.id.btnTakePicture).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mActivity.startPhotoTaker(); } }); /** mActivity.findViewById(R.id.btnAttachFile).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mActivity.startFilePicker(); } });*/ mActivity.findViewById(R.id.btnAttachSticker).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toggleAttachMenu(); showStickers(); } }); mMicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //this is the tap to change to hold to talk mode if (mMicButton.getVisibility() == View.VISIBLE) { mComposeMessage.setVisibility(View.GONE); mMicButton.setVisibility(View.GONE); // Check if no view has focus: View view = mActivity.getCurrentFocus(); if (view != null) { InputMethodManager imm = (InputMethodManager) mActivity .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } mSendButton.setImageResource(R.drawable.ic_keyboard_black_36dp); mSendButton.setVisibility(View.VISIBLE); mButtonTalk.setVisibility(View.VISIBLE); } } }); final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { public void onLongPress(MotionEvent e) { //this is for recording audio directly from one press mActivity.startAudioRecording(); } @Override public boolean onSingleTapUp(MotionEvent e) { if (mActivity.isAudioRecording()) { boolean send = true;//inViewInBounds(mMicButton, (int) motionEvent.getX(), (int) motionEvent.getY()); mActivity.stopAudioRecording(send); } return super.onSingleTapUp(e); } }); mMicButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { return gestureDetector.onTouchEvent(motionEvent); } }); mButtonTalk.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View btnTalk, MotionEvent theMotion) { switch (theMotion.getAction()) { case MotionEvent.ACTION_DOWN: mActivity.startAudioRecording(); mButtonTalk.setText(mActivity.getString(R.string.recording_release)); mViewDeleteVoice.setVisibility(View.VISIBLE); break; case MotionEvent.ACTION_MOVE: boolean inBounds = inViewInBounds(btnTalk, (int) theMotion.getX(), (int) theMotion.getY()); if (!inBounds) mButtonTalk.setText(mActivity.getString(R.string.recording_delete)); else { mButtonTalk.setText(mActivity.getString(R.string.recording_release)); mViewDeleteVoice.setVisibility(View.VISIBLE); } break; case MotionEvent.ACTION_UP: mButtonTalk.setText(mActivity.getString(R.string.push_to_talk)); boolean send = inViewInBounds(btnTalk, (int) theMotion.getX(), (int) theMotion.getY()); mActivity.stopAudioRecording(send); mViewDeleteVoice.setVisibility(View.GONE); break; } return true; } }); /** mHistory.setOnItemLongClickListener(new OnItemLongClickListener () { @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { if (arg1 instanceof MessageView) { String textToCopy = ((MessageView)arg1).getLastMessage(); int sdk = android.os.Build.VERSION.SDK_INT; if(sdk < android.os.Build.VERSION_CODES.HONEYCOMB) { android.text.ClipboardManager clipboard = (android.text.ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE); clipboard.setText(textToCopy); // } else { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE); android.content.ClipData clip = android.content.ClipData.newPlainText("chat",textToCopy); clipboard.setPrimaryClip(clip); // } Toast.makeText(mActivity, mContext.getString(R.string.toast_chat_copied_to_clipboard), Toast.LENGTH_SHORT).show(); return true; } return false; } });**/ mWarningText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showVerifyDialog(); } }); mComposeMessage.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { sendTypingStatus(true); return false; } }); mComposeMessage.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { sendTypingStatus(hasFocus); } }); mComposeMessage.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: sendMessage(); return true; case KeyEvent.KEYCODE_ENTER: if (event.isAltPressed()) { mComposeMessage.append("\n"); return true; } } } return false; } }); mComposeMessage.setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (event != null) { if (event.isAltPressed()) { return false; } } InputMethodManager imm = (InputMethodManager) mContext .getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null && imm.isActive(v)) { imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } sendMessage(); return true; } }); // TODO: this is a hack to implement BUG #1611278, when dispatchKeyEvent() works with // the soft keyboard, we should remove this hack. mComposeMessage.addTextChangedListener(new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int before, int after) { } public void onTextChanged(CharSequence s, int start, int before, int after) { } public void afterTextChanged(Editable s) { doWordSearch(); userActionDetected(); } }); mSendButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mComposeMessage.getVisibility() == View.VISIBLE) sendMessage(); else { mSendButton.setImageResource(R.drawable.ic_send_holo_light); if (mLastSessionStatus == SessionStatus.ENCRYPTED) mSendButton.setImageResource(R.drawable.ic_send_secure); mSendButton.setVisibility(View.GONE); mButtonTalk.setVisibility(View.GONE); mComposeMessage.setVisibility(View.VISIBLE); mMicButton.setVisibility(View.VISIBLE); } } }); mMessageAdapter = new ConversationRecyclerViewAdapter(mActivity, null); mHistory.setAdapter(mMessageAdapter); } private boolean mLastIsTyping = false; private void sendTypingStatus(boolean isTyping) { if (mLastIsTyping != isTyping) { try { mConn.sendTypingStatus(mRemoteAddress, isTyping); } catch (Exception ie) { Log.e(ImApp.LOG_TAG, "error sending typing status", ie); } mLastIsTyping = isTyping; } } DictionarySearch ds = null; PopupMenu mPopupWords = null; SearchWordTask taskSearch = null; private class SearchWordTask extends AsyncTask<String, Long, ArrayList<String>> { private String mLastSearchTerm = null; protected ArrayList<String> doInBackground(String... searchTerm) { String[] searchTerms = searchTerm[0].split(""); if (searchTerms.length > 0) { mLastSearchTerm = searchTerms[searchTerms.length - 1]; ArrayList<String> result = ds.getMatchingWords(mLastSearchTerm); return result; } else return null; } protected void onProgressUpdate(Long... progress) { } protected void onPostExecute(ArrayList<String> result) { if (result != null && result.size() > 0) { if (mPopupWords == null) { mPopupWords = new PopupMenu(mActivity, mComposeMessage); mPopupWords.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { String[] currentText = mComposeMessage.getText().toString().split(""); currentText[currentText.length - 1] = item.toString(); mComposeMessage.setText(""); for (int i = 0; i < currentText.length; i++) { mComposeMessage.append(currentText[i]); if ((i + 1) != currentText.length) mComposeMessage.append(""); } mComposeMessage.setSelection(mComposeMessage.getText().length()); return true; } }); } mPopupWords.getMenu().clear(); for (String item : result) { if (!TextUtils.isEmpty(item)) { SpannableStringBuilder sb = new SpannableStringBuilder(item); sb.setSpan(new CustomTypefaceSpan("", mActivity), 0, item.length(), 0); mPopupWords.getMenu().addSubMenu(sb); } } mPopupWords.show(); } } } private void doWordSearch() { if (Preferences.getUseTibetanDictionary()) { if (ds == null) ds = new DictionarySearch(mActivity); String searchText = mComposeMessage.getText().toString(); if (!TextUtils.isEmpty(searchText)) { if (taskSearch == null || taskSearch.getStatus() == AsyncTask.Status.FINISHED) { taskSearch = new SearchWordTask(); taskSearch.execute(mComposeMessage.getText().toString()); } } } } private boolean inViewInBounds(View view, int x, int y) { Rect outRect = new Rect(); int[] location = new int[2]; view.getHitRect(outRect); return outRect.contains(x, y); } public void startListening() { if (!isServiceUp) return; mIsListening = true; /* if (mViewType == VIEW_TYPE_CHAT) { Cursor cursor = getMessageCursor(); if (cursor == null) { long chatId = getChatId(); if (chatId != -1) startQuery(chatId); } else { //requeryCursor(); } }*/ registerChatListener(); registerForConnEvents(); updateWarningView(); } public void stopListening() { //Cursor cursor = getMessageCursor(); //if (cursor != null && (!cursor.isClosed())) { // cursor.close(); // } cancelRequery(); unregisterChatListener(); unregisterForConnEvents(); mIsListening = false; } void updateChat() { setViewType(VIEW_TYPE_CHAT); //mHistory.invalidate(); checkConnection(); startQuery(getChatId()); // This is not needed, now that there is a ChatView per fragment. It also causes a spurious detection of user action // on fragments adjacent to the current one, when they get initialized. //mComposeMessage.setText(""); updateWarningView(); if (isGroupChat()) { //disable unsupported features for now mActivity.findViewById(R.id.btnAttachPicture).setEnabled(false); mActivity.findViewById(R.id.btnTakePicture).setEnabled(false); //mActivity.findViewById(R.id.btnAttachFile).setEnabled(false); mMicButton.setEnabled(false); ; mActivity.findViewById(R.id.btnAttachPicture).setAlpha(0.2f); mActivity.findViewById(R.id.btnTakePicture).setAlpha(0.2f); //mActivity.findViewById(R.id.btnAttachFile).setAlpha(0.2f); mMicButton.setAlpha(0.5f); } else { mActivity.findViewById(R.id.btnAttachPicture).setEnabled(true); mActivity.findViewById(R.id.btnTakePicture).setEnabled(true); //mActivity.findViewById(R.id.btnAttachFile).setEnabled(true); mMicButton.setEnabled(true); } } int mContactType = -1; private void updateSessionInfo(Cursor c) { if (c != null && (!c.isClosed())) { mProviderId = c.getLong(PROVIDER_COLUMN); mAccountId = c.getLong(ACCOUNT_COLUMN); mPresenceStatus = c.getInt(PRESENCE_STATUS_COLUMN); mContactType = c.getInt(TYPE_COLUMN); mRemoteNickname = c.getString(NICKNAME_COLUMN); mRemoteAddress = c.getString(USERNAME_COLUMN); mSubscriptionType = c.getInt(SUBSCRIPTION_TYPE_COLUMN); mSubscriptionStatus = c.getInt(SUBSCRIPTION_STATUS_COLUMN); if (mSubscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING) { // bindSubscription(mProviderId, mRemoteAddress); new android.support.v7.app.AlertDialog.Builder(mActivity) .setTitle(mActivity.getString(R.string.add_friends)) .setMessage(mContext.getString(R.string.subscription_notify_text, mRemoteAddress)) .setPositiveButton(mActivity.getString(R.string.accept_invitation), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { approveSubscription(); } }) .setNegativeButton(mActivity.getString(R.string.decline_subscription), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // do nothing } }) .setIcon(android.R.drawable.ic_dialog_alert).show(); } } } public String getTitle() { return mRemoteNickname; } public Date getLastSeen() { return mLastSeen; } public RoundedAvatarDrawable getIcon() { return mRemoteAvatar; } public Drawable getHeader() { return mRemoteHeader; } private void updatePresenceDisplay() { if (mRemoteAvatar == null) return; switch (mPresenceStatus) { case Presence.AVAILABLE: mRemoteAvatar.setBorderColor(mActivity.getResources().getColor(R.color.holo_green_light)); mRemoteAvatar.setAlpha(255); break; case Presence.IDLE: mRemoteAvatar.setBorderColor(mActivity.getResources().getColor(R.color.holo_green_dark)); mRemoteAvatar.setAlpha(255); break; case Presence.AWAY: mRemoteAvatar.setBorderColor(mActivity.getResources().getColor(R.color.holo_orange_light)); mRemoteAvatar.setAlpha(255); break; case Presence.DO_NOT_DISTURB: mRemoteAvatar.setBorderColor(mActivity.getResources().getColor(R.color.holo_red_dark)); mRemoteAvatar.setAlpha(255); break; case Presence.OFFLINE: mRemoteAvatar.setBorderColor(mActivity.getResources().getColor(R.color.holo_grey_light)); mRemoteAvatar.setAlpha(100); break; default: } } private void setGroupTitle() { if (mContactType == Imps.Contacts.TYPE_GROUP) { StringBuilder buf = new StringBuilder(); int count = -1; try { buf.append(mCurrentChatSession.getName()); count = mCurrentChatSession.getParticipants().length; } catch (Exception e) { } if (count > 0) { buf.append(" ("); buf.append(count); buf.append(")"); } /* final String[] projection = { Imps.GroupMembers.NICKNAME }; Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, mLastChatId); ContentResolver cr = mActivity.getContentResolver(); Cursor c = cr.query(memberUri, projection, null, null, null); StringBuilder buf = new StringBuilder(); buf.append(mActivity.getString(R.string.menu_new_group_chat)); if (c != null) { buf.append(" ("); buf.append(c.getCount()); buf.append(")"); c.close(); } */ mRemoteNickname = buf.toString(); } } private void deleteChat() { Uri chatUri = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mLastChatId); mActivity.getContentResolver().delete(chatUri, null, null); } public void bindChat(long chatId, String address, String name) { //log("bind " + this + " " + chatId); mLastChatId = chatId; setViewType(VIEW_TYPE_CHAT); Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatId); Cursor c = mActivity.getContentResolver().query(contactUri, CHAT_PROJECTION, null, null, null); if (c == null) return; if (!c.moveToFirst()) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("Failed to query chat: " + chatId); } mLastChatId = -1; c.close(); } else { updateSessionInfo(c); if (mRemoteAvatar == null) { try { mRemoteAvatar = DatabaseUtils.getAvatarFromCursor(c, AVATAR_COLUMN, ImApp.DEFAULT_AVATAR_WIDTH, ImApp.DEFAULT_AVATAR_HEIGHT); } catch (Exception e) { } if (mRemoteAvatar == null) { mRemoteAvatar = new RoundedAvatarDrawable( BitmapFactory.decodeResource(mActivity.getResources(), R.drawable.avatar_unknown)); } updatePresenceDisplay(); } if (mRemoteHeader == null) { try { mRemoteHeader = DatabaseUtils.getHeaderImageFromCursor(c, AVATAR_COLUMN, ImApp.DEFAULT_AVATAR_WIDTH, ImApp.DEFAULT_AVATAR_HEIGHT); } catch (Exception e) { } } c.close(); mCurrentChatSession = getChatSession(); if (mCurrentChatSession == null) mCurrentChatSession = createChatSession(); if (mCurrentChatSession != null) { isServiceUp = true; } updateChat(); if (mRemoteNickname == null) if (TextUtils.isEmpty(name)) setGroupTitle(); else mRemoteNickname = name; try { mRemoteNickname = mRemoteNickname.split("@")[0].split("\\.")[0]; } catch (Exception e) { //handle glitches in unicode nicknames } } } public void bindInvitation(long invitationId) { Uri uri = ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId); ContentResolver cr = mActivity.getContentResolver(); Cursor cursor = cr.query(uri, INVITATION_PROJECT, null, null, null); try { if (!cursor.moveToFirst()) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("Failed to query invitation: " + invitationId); } // mNewChatActivity.finish(); } else { setViewType(VIEW_TYPE_INVITATION); mInvitationId = cursor.getLong(INVITATION_ID_COLUMN); mProviderId = cursor.getLong(INVITATION_PROVIDER_COLUMN); String sender = cursor.getString(INVITATION_SENDER_COLUMN); TextView mInvitationText = (TextView) mActivity.findViewById(R.id.txtInvitation); mInvitationText.setText(mContext.getString(R.string.invitation_prompt, sender)); // mNewChatActivity.setTitle(mContext.getString(R.string.chat_with, sender)); } } finally { cursor.close(); } } /** public void bindSubscription(long providerId, String from) { mProviderId = providerId; // mRemoteAddressString = from; setViewType(VIEW_TYPE_SUBSCRIPTION); TextView text = (TextView) mActivity.findViewById(R.id.txtSubscription); String displayableAddr = ImpsAddressUtils.getDisplayableAddress(from); text.setText(mContext.getString(R.string.subscription_prompt, displayableAddr)); //.displayableAdd mNewChatActivity.setTitle(mContext.getString(R.string.chat_with, displayableAddr)); mApp.dismissChatNotification(providerId, from); }*/ private void setViewType(int type) { mViewType = type; if (type == VIEW_TYPE_CHAT) { mActivity.findViewById(R.id.invitationPanel).setVisibility(View.GONE); // mActivity.findViewById(R.id.subscription).setVisibility(View.GONE); setChatViewEnabled(true); } else if (type == VIEW_TYPE_INVITATION) { //setChatViewEnabled(false); mActivity.findViewById(R.id.invitationPanel).setVisibility(View.VISIBLE); mActivity.findViewById(R.id.btnAccept).requestFocus(); } else if (type == VIEW_TYPE_SUBSCRIPTION) { //setChatViewEnabled(false); // mActivity.findViewById(R.id.subscription).setVisibility(View.VISIBLE); // mActivity.findViewById(R.id.btnApproveSubscription).requestFocus(); } } private void setChatViewEnabled(boolean enabled) { mComposeMessage.setEnabled(enabled); mSendButton.setEnabled(enabled); if (enabled) { // This can steal focus from the fragment that's i n front of the user //mComposeMessage.requestFocus(); } else { mHistory.setAdapter(null); } } RecyclerView getHistoryView() { return mHistory; } private Uri mUri; private LoaderManager mLoaderManager; private int loaderId = 100001; private synchronized void startQuery(long chatId) { mUri = Imps.Messages.getContentUriByThreadId(chatId); mLoaderManager = mActivity.getSupportLoaderManager(); if (mLoaderManager == null) mLoaderManager.initLoader(loaderId++, null, new MyLoaderCallbacks()); else mLoaderManager.restartLoader(loaderId++, null, new MyLoaderCallbacks()); } class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { CursorLoader loader = new CursorLoader(mActivity, mUri, null, null, null, Imps.Messages.DEFAULT_SORT_ORDER); return loader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor newCursor) { if (newCursor != null) { newCursor.setNotificationUri(mActivity.getApplicationContext().getContentResolver(), mUri); mMessageAdapter.swapCursor(new DeltaCursor(newCursor)); if (!mMessageAdapter.isScrolling()) { mHandler.post(new Runnable() { public void run() { if (mMessageAdapter.getItemCount() > 0) { mHistory.getLayoutManager().scrollToPosition(mMessageAdapter.getItemCount() - 1); } } }); } } } @Override public void onLoaderReset(Loader<Cursor> loader) { mMessageAdapter.swapCursor(null); } } void scheduleRequery(long interval) { if (mRequeryCallback == null) { mRequeryCallback = new RequeryCallback(); } else { mHandler.removeCallbacks(mRequeryCallback); } if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("scheduleRequery"); } mHandler.postDelayed(mRequeryCallback, interval); } void cancelRequery() { if (mRequeryCallback != null) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("cancelRequery"); } mHandler.removeCallbacks(mRequeryCallback); mRequeryCallback = null; } } void requeryCursor() { mLoaderManager.restartLoader(loaderId++, null, new MyLoaderCallbacks()); updateWarningView(); /** if (mMessageAdapter.isScrolling()) { mMessageAdapter.setNeedRequeryCursor(true); return; } // This is redundant if there are messages in view, because the cursor requery will update everything. // However, if there are no messages, no update will trigger below, and we still want this to update. // TODO: async query? Cursor cursor = getMessageCursor(); if (cursor != null) { cursor.requery(); }*/ } private Cursor getMessageCursor() { return mMessageAdapter == null ? null : mMessageAdapter.getCursor(); } public void refreshSession() { setOTRState(false); setOTRState(true); } public void closeChatSession(boolean doDelete) { if (getChatSession() != null) { try { setOTRState(false); updateWarningView(); getChatSession().leave(); } catch (RemoteException e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "send message error", e); } } if (doDelete) deleteChat(); } /** public void verifyScannedFingerprint (String scannedFingerprint) { try { IOtrChatSession otrChatSession = mCurrentChatSession.getDefaultOtrChatSession(); if (scannedFingerprint != null && scannedFingerprint.equalsIgnoreCase(otrChatSession.getRemoteFingerprint())) { verifyRemoteFingerprint(); } } catch (RemoteException e) { LogCleaner.error(ImApp.LOG_TAG, "unable to perform manual key verification", e); } }*/ public void showVerifyDialog() { Intent intent = new Intent(mContext, ContactDisplayActivity.class); intent.putExtra("nickname", mRemoteNickname); intent.putExtra("address", mRemoteAddress); intent.putExtra("provider", mProviderId); intent.putExtra("account", mAccountId); intent.putExtra("contactId", mLastChatId); try { IOtrChatSession otrChatSession = mCurrentChatSession.getDefaultOtrChatSession(); intent.putExtra("fingerprint", otrChatSession.getRemoteFingerprint()); } catch (RemoteException re) { } mContext.startActivity(intent); } public void showGroupInfo() { Intent intent = new Intent(mContext, GroupDisplayActivity.class); intent.putExtra("nickname", mRemoteNickname); intent.putExtra("address", mRemoteAddress); intent.putExtra("provider", mProviderId); intent.putExtra("account", mAccountId); intent.putExtra("chat", mLastChatId); mContext.startActivity(intent); } public void blockContact() { // TODO: unify with codes in ContactListView DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { try { checkConnection(); mConn = mApp.getConnection(mProviderId, mAccountId); IContactListManager manager = mConn.getContactListManager(); manager.blockContact(Address.stripResource(mRemoteAddress)); // mNewChatActivity.finish(); } catch (Exception e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "send message error", e); } } }; Resources r = mActivity.getResources(); // The positive button is deliberately set as no so that // the no is the default value new AlertDialog.Builder(mContext).setTitle(R.string.confirm) .setMessage(r.getString(R.string.confirm_block_contact, mRemoteNickname)) .setPositiveButton(R.string.yes, confirmListener) // default button .setNegativeButton(R.string.no, null).setCancelable(false).show(); } public long getProviderId() { return mProviderId; } public long getAccountId() { return mAccountId; } public long getChatId() { return mLastChatId; } private IChatSession createChatSession() { try { if (mConn != null) { IChatSessionManager sessionMgr = mConn.getChatSessionManager(); if (sessionMgr != null) { String remoteAddress = mRemoteAddress; IChatSession session = null; if (mContactType == Imps.Contacts.TYPE_GROUP) { new ChatSessionInitTask(((ImApp) mActivity.getApplication()), mProviderId, mAccountId, Imps.Contacts.TYPE_GROUP, false).executeOnExecutor(ImApp.sThreadPoolExecutor, remoteAddress); } else { remoteAddress = Address.stripResource(mRemoteAddress); session = sessionMgr.createChatSession(remoteAddress, false); } return session; } } } catch (Exception e) { //mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "issue getting chat session", e); } return null; } public IChatSession getChatSession() { try { if (checkConnection()) { if (mConn != null) { IChatSessionManager sessionMgr = mConn.getChatSessionManager(); if (sessionMgr != null) { IChatSession session = sessionMgr.getChatSession(Address.stripResource(mRemoteAddress)); return session; } } } } catch (Exception e) { //mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "error getting chat session", e); } return null; } public boolean isGroupChat() { return this.mContactType == Imps.Contacts.TYPE_GROUP; } void sendMessage() { String msg = mComposeMessage.getText().toString(); //new SendMessageAsyncTask().execute(msg); sendMessageAsync(msg); sendTypingStatus(false); /** if (ds != null) { ds.close(); }*/ } void sendMessageAsync(final String msg) { //new SendMessageAsyncTask().execute(msg); new Thread() { public void run() { sendMessage(msg, false); } }.start(); mComposeMessage.setText(""); mComposeMessage.requestFocus(); } boolean sendMessage(String msg, boolean isResend) { //don't send empty messages if (TextUtils.isEmpty(msg.trim())) { return false; } //if the message starts with a command (just /giphy for now) do something else if (msg.startsWith("/giphy ")) { return doGiphy(msg); } //otherwise get the session, create if necessary, and then send IChatSession session = getChatSession(); if (session == null) session = createChatSession(); if (session != null) { try { session.sendMessage(msg, isResend); return true; //requeryCursor(); } catch (RemoteException e) { // mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "send message error", e); } catch (Exception e) { // mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "send message error", e); } } return false; } private static GiphyAPI.Monitor mMonitor = null; private synchronized boolean doGiphy(String search) { mMonitor = new GiphyAPI.Monitor() { public void onSearchComplete(GiphyAPI.SearchResult result) { if (result.data != null && result.data.length > 0) { final GiphyAPI.GifImage gifResult = result.data[0].images.original; new Thread() { public void run() { mActivity.handleSendDelete(getChatSession(), Uri.parse(gifResult.url), "image/gif", false, false, true); } }.start(); } else { Toast.makeText(mActivity, "No giphy stickers available for your search", Toast.LENGTH_SHORT) .show(); } GiphyAPI.get().removeMonitor(mMonitor); mMonitor = null; } }; GiphyAPI.get().addMonitor(mMonitor); try { GiphyAPI.get().search(URLEncoder.encode(search.substring(7).trim(), "UTF-8")); } catch (Exception e) { } return true; } void registerChatListener() { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("registerChatListener " + mLastChatId); } try { if (getChatSession() != null) { getChatSession().registerChatListener(mChatListener); } checkConnection(); if (mConn != null) { IContactListManager listMgr = mConn.getContactListManager(); listMgr.registerContactListListener(mContactListListener); } } catch (Exception e) { Log.w(ImApp.LOG_TAG, "<ChatView> registerChatListener fail:" + e.getMessage()); } } void unregisterChatListener() { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("unregisterChatListener " + mLastChatId); } try { if (getChatSession() != null) { getChatSession().unregisterChatListener(mChatListener); } checkConnection(); if (mConn != null) { IContactListManager listMgr = mConn.getContactListManager(); listMgr.unregisterContactListListener(mContactListListener); } } catch (Exception e) { Log.w(ImApp.LOG_TAG, "<ChatView> unregisterChatListener fail:" + e.getMessage()); } } void updateWarningView() { int visibility = View.GONE; int iconVisibility = View.GONE; String message = null; boolean isConnected; try { checkConnection(); isConnected = (mConn == null) ? false : mConn.getState() == ImConnection.LOGGED_IN; } catch (Exception e) { isConnected = false; } if (this.isGroupChat()) { mComposeMessage.setHint(R.string.this_is_a_group_chat); } else if (mCurrentChatSession != null) { IOtrChatSession otrChatSession = null; try { otrChatSession = mCurrentChatSession.getDefaultOtrChatSession(); //check if the chat is otr or not if (otrChatSession != null) { try { mLastSessionStatus = SessionStatus.values()[otrChatSession.getChatStatus()]; } catch (RemoteException e) { Log.w("Gibber", "Unable to call remote OtrChatSession from ChatView", e); } } } catch (RemoteException e) { LogCleaner.error(ImApp.LOG_TAG, "error getting OTR session in ChatView", e); } if (mContactType == Imps.Contacts.TYPE_GROUP) { message = ""; } else if ((mSubscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM)) { // bindSubscription(mProviderId, mRemoteAddress); visibility = View.VISIBLE; } else { visibility = View.GONE; } if (mLastSessionStatus == SessionStatus.PLAINTEXT) { mSendButton.setImageResource(R.drawable.ic_send_holo_light); mComposeMessage.setHint(R.string.compose_hint); } else if (mLastSessionStatus == SessionStatus.ENCRYPTED) { if (mIsStartingOtr) { mIsStartingOtr = false; //it's started! } if (mSendButton.getVisibility() == View.GONE) { mComposeMessage.setHint(R.string.compose_hint_secure); mSendButton.setImageResource(R.drawable.ic_send_secure); } try { String rFingerprint = otrChatSession.getRemoteFingerprint(); mIsVerified = (OtrChatManager.getInstance().isRemoteKeyVerified(mRemoteAddress, rFingerprint)); } catch (RemoteException re) { } } else if (mLastSessionStatus == SessionStatus.FINISHED) { mSendButton.setImageResource(R.drawable.ic_send_holo_light); mComposeMessage.setHint(R.string.compose_hint); mWarningText.setTextColor(Color.WHITE); mStatusWarningView.setBackgroundColor(Color.DKGRAY); message = mContext.getString(R.string.otr_session_status_finished); visibility = View.VISIBLE; } } mStatusWarningView.setVisibility(visibility); if (visibility == View.VISIBLE) { if (message != null && message.length() > 0) { mWarningText.setText(message); mWarningText.setVisibility(View.VISIBLE); } else { mWarningText.setVisibility(View.GONE); } } } public SessionStatus getOtrSessionStatus() { return mLastSessionStatus; } public boolean isOtrSessionVerified() { return mIsVerified; } public int getRemotePresence() { return mPresenceStatus; } /* @Override public boolean dispatchKeyEvent(KeyEvent event) { userActionDetected(); return mActivity.dispatchKeyEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { try { userActionDetected(); return mActivity.dispatchTouchEvent(ev); } catch (ActivityNotFoundException e) { // if the user clicked a link, e.g. geo:60.17,24.829, and there is // no app to handle that kind of link, catch the exception Toast.makeText(mActivity, R.string.error_no_app_to_handle_url, Toast.LENGTH_SHORT) .show(); return true; } } @Override public boolean dispatchTrackballEvent(MotionEvent ev) { userActionDetected(); return mActivity.dispatchTrackballEvent(ev); }*/ private void userActionDetected() { // Check that we have a chat session and that our fragment is resumed // The latter filters out bogus TextWatcher events on restore from saved if (getChatSession() != null && mIsListening) { try { getChatSession().markAsRead(); //updateWarningView(); } catch (RemoteException e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "send message error", e); } } toggleInputMode(); } private void toggleInputMode() { if (mButtonTalk.getVisibility() == View.GONE) { if (mComposeMessage.getText().length() > 0 && mSendButton.getVisibility() == View.GONE) { mMicButton.setVisibility(View.GONE); mSendButton.setVisibility(View.VISIBLE); mSendButton.setImageResource(R.drawable.ic_send_holo_light); if (mLastSessionStatus == SessionStatus.ENCRYPTED) mSendButton.setImageResource(R.drawable.ic_send_secure); } else if (mComposeMessage.getText().length() == 0) { mMicButton.setVisibility(View.VISIBLE); mSendButton.setVisibility(View.GONE); } } } private final class ChatViewHandler extends SimpleAlertHandler { public ChatViewHandler(Activity activity) { super(activity); } @Override public void handleMessage(Message msg) { long providerId = ((long) msg.arg1 << 32) | msg.arg2; if (providerId != mProviderId) { return; } switch (msg.what) { case ImApp.EVENT_CONNECTION_DISCONNECTED: log("Handle event connection disconnected."); updateWarningView(); promptDisconnectedEvent(msg); return; case PROMPT_FOR_DATA_TRANSFER: showPromptForData(msg.getData().getString("from"), msg.getData().getString("file")); break; case SHOW_DATA_ERROR: String fileName = msg.getData().getString("file"); String error = msg.getData().getString("err"); Toast.makeText(mContext, "Error transferring file: " + error, Toast.LENGTH_LONG).show(); break; case SHOW_DATA_PROGRESS: int percent = msg.getData().getInt("progress"); break; case SHOW_TYPING: boolean isTyping = msg.getData().getBoolean("typing"); mActivity.findViewById(R.id.tvTyping).setVisibility(isTyping ? View.VISIBLE : View.GONE); default: updateWarningView(); } super.handleMessage(msg); } } public static class DeltaCursor implements Cursor { static final String DELTA_COLUMN_NAME = "delta"; private Cursor mInnerCursor; private String[] mColumnNames; private int mDateColumn = -1; private int mDeltaColumn = -1; DeltaCursor(Cursor cursor) { mInnerCursor = cursor; String[] columnNames = cursor.getColumnNames(); int len = columnNames.length; mColumnNames = new String[len + 1]; for (int i = 0; i < len; i++) { mColumnNames[i] = columnNames[i]; if (mColumnNames[i].equals(Imps.Messages.DATE)) { mDateColumn = i; } } mDeltaColumn = len; mColumnNames[mDeltaColumn] = DELTA_COLUMN_NAME; //if (DBG) log("##### DeltaCursor constructor: mDeltaColumn=" + // mDeltaColumn + ", columnName=" + mColumnNames[mDeltaColumn]); } public int getCount() { return mInnerCursor.getCount(); } public int getPosition() { return mInnerCursor.getPosition(); } public boolean move(int offset) { return mInnerCursor.move(offset); } public boolean moveToPosition(int position) { return mInnerCursor.moveToPosition(position); } public boolean moveToFirst() { return mInnerCursor.moveToFirst(); } public boolean moveToLast() { return mInnerCursor.moveToLast(); } public boolean moveToNext() { return mInnerCursor.moveToNext(); } public boolean moveToPrevious() { return mInnerCursor.moveToPrevious(); } public boolean isFirst() { return mInnerCursor.isFirst(); } public boolean isLast() { return mInnerCursor.isLast(); } public boolean isBeforeFirst() { return mInnerCursor.isBeforeFirst(); } public boolean isAfterLast() { return mInnerCursor.isAfterLast(); } public int getColumnIndex(String columnName) { if (DELTA_COLUMN_NAME.equals(columnName)) { return mDeltaColumn; } int columnIndex = mInnerCursor.getColumnIndex(columnName); return columnIndex; } public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { if (DELTA_COLUMN_NAME.equals(columnName)) { return mDeltaColumn; } return mInnerCursor.getColumnIndexOrThrow(columnName); } public String getColumnName(int columnIndex) { if (columnIndex == mDeltaColumn) { return DELTA_COLUMN_NAME; } return mInnerCursor.getColumnName(columnIndex); } public int getColumnCount() { return mInnerCursor.getColumnCount() + 1; } public void deactivate() { mInnerCursor.deactivate(); } public boolean requery() { return mInnerCursor.requery(); } public void close() { mInnerCursor.close(); } public boolean isClosed() { return mInnerCursor.isClosed(); } public void registerContentObserver(ContentObserver observer) { mInnerCursor.registerContentObserver(observer); } public void unregisterContentObserver(ContentObserver observer) { mInnerCursor.unregisterContentObserver(observer); } public void registerDataSetObserver(DataSetObserver observer) { mInnerCursor.registerDataSetObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mInnerCursor.unregisterDataSetObserver(observer); } public void setNotificationUri(ContentResolver cr, Uri uri) { mInnerCursor.setNotificationUri(cr, uri); } public boolean getWantsAllOnMoveCalls() { return mInnerCursor.getWantsAllOnMoveCalls(); } @Override public void setExtras(Bundle bundle) { } public Bundle getExtras() { return mInnerCursor.getExtras(); } public Bundle respond(Bundle extras) { return mInnerCursor.respond(extras); } public String[] getColumnNames() { return mColumnNames; } private void checkPosition() { int pos = mInnerCursor.getPosition(); int count = mInnerCursor.getCount(); if (-1 == pos || count == pos) { throw new CursorIndexOutOfBoundsException(pos, count); } } public byte[] getBlob(int column) { checkPosition(); if (column == mDeltaColumn) { return null; } return mInnerCursor.getBlob(column); } public String getString(int column) { checkPosition(); if (column == mDeltaColumn) { long value = getDeltaValue(); return Long.toString(value); } return mInnerCursor.getString(column); } public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { checkPosition(); if (columnIndex == mDeltaColumn) { long value = getDeltaValue(); String strValue = Long.toString(value); int len = strValue.length(); char[] data = buffer.data; if (data == null || data.length < len) { buffer.data = strValue.toCharArray(); } else { strValue.getChars(0, len, data, 0); } buffer.sizeCopied = strValue.length(); } else { mInnerCursor.copyStringToBuffer(columnIndex, buffer); } } public short getShort(int column) { checkPosition(); if (column == mDeltaColumn) { return (short) getDeltaValue(); } return mInnerCursor.getShort(column); } public int getInt(int column) { checkPosition(); if (column == mDeltaColumn) { return (int) getDeltaValue(); } return mInnerCursor.getInt(column); } public long getLong(int column) { //if (DBG) log("DeltaCursor.getLong: column=" + column + ", mDeltaColumn=" + mDeltaColumn); checkPosition(); if (column == mDeltaColumn) { return getDeltaValue(); } return mInnerCursor.getLong(column); } public float getFloat(int column) { checkPosition(); if (column == mDeltaColumn) { return getDeltaValue(); } return mInnerCursor.getFloat(column); } public double getDouble(int column) { checkPosition(); if (column == mDeltaColumn) { return getDeltaValue(); } return mInnerCursor.getDouble(column); } public boolean isNull(int column) { checkPosition(); if (column == mDeltaColumn) { return false; } return mInnerCursor.isNull(column); } private long getDeltaValue() { int pos = mInnerCursor.getPosition(); //Log.i(LOG_TAG, "getDeltaValue: mPos=" + mPos); long t2, t1; if (pos == getCount() - 1) { t1 = mInnerCursor.getLong(mDateColumn); t2 = System.currentTimeMillis(); } else { mInnerCursor.moveToPosition(pos + 1); t2 = mInnerCursor.getLong(mDateColumn); mInnerCursor.moveToPosition(pos); t1 = mInnerCursor.getLong(mDateColumn); } return t2 - t1; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public int getType(int arg0) { return mInnerCursor.getType(arg0); } @TargetApi(19) @Override public Uri getNotificationUri() { return mInnerCursor.getNotificationUri(); } } public class ConversationRecyclerViewAdapter extends CursorRecyclerViewAdapter<MessageViewHolder> { private int mScrollState; private boolean mNeedRequeryCursor; private int mNicknameColumn; private int mBodyColumn; private int mDateColumn; private int mTypeColumn; private int mErrCodeColumn; private int mDeltaColumn; private int mDeliveredColumn; private int mMimeTypeColumn; private int mIdColumn; private ActionMode mActionMode; private View mLastSelectedView; public ConversationRecyclerViewAdapter(Activity context, Cursor c) { super(context, c); if (c != null) { resolveColumnIndex(c); } setHasStableIds(true); } private void resolveColumnIndex(Cursor c) { mNicknameColumn = c.getColumnIndexOrThrow(Imps.Messages.NICKNAME); mBodyColumn = c.getColumnIndexOrThrow(Imps.Messages.BODY); mDateColumn = c.getColumnIndexOrThrow(Imps.Messages.DATE); mTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.TYPE); mErrCodeColumn = c.getColumnIndexOrThrow(Imps.Messages.ERROR_CODE); mDeltaColumn = c.getColumnIndexOrThrow(DeltaCursor.DELTA_COLUMN_NAME); mDeliveredColumn = c.getColumnIndexOrThrow(Imps.Messages.IS_DELIVERED); mMimeTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.MIME_TYPE); mIdColumn = c.getColumnIndexOrThrow(Imps.Messages._ID); } @Override public Cursor swapCursor(Cursor newCursor) { if (newCursor != null) { resolveColumnIndex(newCursor); } return super.swapCursor(newCursor); } @Override public long getItemId(int position) { Cursor c = getCursor(); c.moveToPosition(position); long chatId = c.getLong(mIdColumn); return chatId; } @Override public int getItemViewType(int position) { Cursor c = getCursor(); c.moveToPosition(position); int type = c.getInt(mTypeColumn); boolean isLeft = (type == Imps.MessageType.INCOMING_ENCRYPTED) || (type == Imps.MessageType.INCOMING) || (type == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED); if (isLeft) return 0; else return 1; } public Cursor getItem(int position) { Cursor c = getCursor(); c.moveToPosition(position); return c; } void setLinkifyForMessageView(MessageListItem messageView) { try { if (messageView == null) return; if (mConn != null) messageView.setLinkify(!mConn.isUsingTor() || Preferences.getLinkifyOnTor()); } catch (RemoteException e) { e.printStackTrace(); messageView.setLinkify(false); } } @Override public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { MessageListItem view = null; if (viewType == 0) view = (MessageListItem) LayoutInflater.from(parent.getContext()) .inflate(R.layout.message_view_left, parent, false); else view = (MessageListItem) LayoutInflater.from(parent.getContext()) .inflate(R.layout.message_view_right, parent, false); view.setOnLongClickListener(new View.OnLongClickListener() { // Called when the user long-clicks on someView public boolean onLongClick(View view) { if (mActionMode != null) { return false; } mLastSelectedView = view; // Start the CAB using the ActionMode.Callback defined above mActionMode = ((Activity) mContext).startActionMode(mActionModeCallback); return true; } }); MessageViewHolder mvh = new MessageViewHolder(view); view.applyStyleColors(); return mvh; } @Override public void onBindViewHolder(MessageViewHolder viewHolder, Cursor cursor) { MessageListItem messageView = (MessageListItem) viewHolder.itemView; setLinkifyForMessageView(messageView); messageView.applyStyleColors(); int messageType = cursor.getInt(mTypeColumn); String nickname = isGroupChat() ? cursor.getString(mNicknameColumn) : mRemoteNickname; String mimeType = cursor.getString(mMimeTypeColumn); int id = cursor.getInt(mIdColumn); String body = cursor.getString(mBodyColumn); long delta = cursor.getLong(mDeltaColumn); boolean showTimeStamp = true;//(delta > SHOW_TIME_STAMP_INTERVAL); long timestamp = cursor.getLong(mDateColumn); Date date = showTimeStamp ? new Date(timestamp) : null; boolean isDelivered = cursor.getLong(mDeliveredColumn) > 0; long showDeliveryInterval = (mimeType == null) ? SHOW_DELIVERY_INTERVAL : SHOW_MEDIA_DELIVERY_INTERVAL; boolean showDelivery = ((System.currentTimeMillis() - timestamp) > showDeliveryInterval); DeliveryState deliveryState = DeliveryState.NEUTRAL; if (showDelivery && !isDelivered && mExpectingDelivery) { deliveryState = DeliveryState.UNDELIVERED; } else if (isDelivered) { deliveryState = DeliveryState.DELIVERED; } if (!mExpectingDelivery && isDelivered) { mExpectingDelivery = true; } else if (cursor.getPosition() == cursor.getCount() - 1) { /* // if showTimeStamp is false for the latest message, then set a timer to query the // cursor again in a minute, so we can update the last message timestamp if no new // message is received if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("delta = " + delta + ", showTs=" + showTimeStamp); } *//* if (!showDelivery) { scheduleRequery(SHOW_DELIVERY_INTERVAL); } else if (!showTimeStamp) { scheduleRequery(SHOW_TIME_STAMP_INTERVAL); } else { cancelRequery(); }*/ } EncryptionState encState = EncryptionState.NONE; if (messageType == Imps.MessageType.INCOMING_ENCRYPTED) { messageType = Imps.MessageType.INCOMING; encState = EncryptionState.ENCRYPTED; } else if (messageType == Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED) { messageType = Imps.MessageType.INCOMING; encState = EncryptionState.ENCRYPTED_AND_VERIFIED; } else if (messageType == Imps.MessageType.OUTGOING_ENCRYPTED) { messageType = Imps.MessageType.OUTGOING; encState = EncryptionState.ENCRYPTED; } else if (messageType == Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED) { messageType = Imps.MessageType.OUTGOING; encState = EncryptionState.ENCRYPTED_AND_VERIFIED; } switch (messageType) { case Imps.MessageType.INCOMING: messageView.bindIncomingMessage(viewHolder, id, messageType, mRemoteAddress, nickname, mimeType, body, date, mMarkup, false, encState, isGroupChat(), mPresenceStatus); break; case Imps.MessageType.OUTGOING: case Imps.MessageType.POSTPONED: int errCode = cursor.getInt(mErrCodeColumn); if (errCode != 0) { messageView.bindErrorMessage(errCode); } else { messageView.bindOutgoingMessage(viewHolder, id, messageType, null, mimeType, body, date, mMarkup, false, deliveryState, encState); } break; default: messageView.bindPresenceMessage(viewHolder, nickname, messageType, date, isGroupChat(), false); } } public void onScrollStateChanged(AbsListView view, int scrollState) { int oldState = mScrollState; mScrollState = scrollState; if (getChatSession() != null) { try { getChatSession().markAsRead(); } catch (RemoteException e) { mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "send message error", e); } } if (oldState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { if (mNeedRequeryCursor) { requeryCursor(); } else { notifyDataSetChanged(); } } } boolean isScrolling() { return mScrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING; } void setNeedRequeryCursor(boolean requeryCursor) { mNeedRequeryCursor = requeryCursor; } ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { // Called when the action mode is created; startActionMode() was called @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // Inflate a menu resource providing context menu items MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.menu_message_context, menu); return true; } // Called each time the action mode is shown. Always called after onCreateActionMode, but // may be called multiple times if the mode is invalidated. @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; // Return false if nothing is done } // Called when the user selects a contextual menu item @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { /** case R.id.menu_message_delete: //shareCurrentItem(); mode.finish(); // Action picked, so close the CAB return true; */ case R.id.menu_message_share: ((MessageListItem) mLastSelectedView).exportMediaFile(); mode.finish(); // Action picked, so close the CAB return true; case R.id.menu_message_forward: ((MessageListItem) mLastSelectedView).forwardMediaFile(); mode.finish(); // Action picked, so close the CAB return true; case R.id.menu_message_resend: sendMessageAsync(((MessageListItem) mLastSelectedView).getLastMessage()); mode.finish(); // Action picked, so close the CAB return true; default: return false; } } // Called when the user exits the action mode @Override public void onDestroyActionMode(ActionMode mode) { mActionMode = null; if (mLastSelectedView != null) mLastSelectedView.setSelected(false); } }; } public Cursor getMessageAtPosition(int position) { Object item = mMessageAdapter.getItem(position); return (Cursor) item; } public EditText getComposedMessage() { return mComposeMessage; } public void onServiceConnected() { if (!isServiceUp) { bindChat(mLastChatId, null, null); startListening(); } } private void toggleAttachMenu() { if (mViewAttach.getVisibility() == View.INVISIBLE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // get the center for the clipping circle int cx = mViewAttach.getLeft(); int cy = mViewAttach.getHeight(); // get the final radius for the clipping circle float finalRadius = (float) Math.hypot(cx, cy); // create the animator for this view (the start radius is zero) Animator anim = ViewAnimationUtils.createCircularReveal(mViewAttach, cx, cy, 0, finalRadius); // make the view visible and start the animation mViewAttach.setVisibility(View.VISIBLE); anim.start(); } else { mViewAttach.setVisibility(View.VISIBLE); } // Check if no view has focus: View view = mActivity.getCurrentFocus(); if (view != null) { InputMethodManager imm = (InputMethodManager) mActivity .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // get the center for the clipping circle int cx = mViewAttach.getLeft(); int cy = mViewAttach.getHeight(); // get the initial radius for the clipping circle float initialRadius = (float) Math.hypot(cx, cy); // create the animation (the final radius is zero) Animator anim = ViewAnimationUtils.createCircularReveal(mViewAttach, cx, cy, initialRadius, 0); // make the view invisible when the animation is done anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mViewAttach.setVisibility(View.INVISIBLE); } }); // start the animation anim.start(); } else { mViewAttach.setVisibility(View.INVISIBLE); } } if (mStickerBox != null) mStickerBox.setVisibility(View.GONE); } private ViewPager mStickerPager = null; private View mStickerBox = null; private void showStickers() { if (mStickerPager == null) { initStickers(); mStickerBox = mActivity.findViewById(R.id.stickerBox); } mStickerBox.setVisibility(mStickerBox.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); } private synchronized void initStickers() { mStickerPager = (ViewPager) mActivity.findViewById(R.id.stickerPager); Collection<StickerGroup> emojiGroups = StickerManager.getInstance(mActivity).getEmojiGroups(); if (emojiGroups.size() > 0) { StickerPagerAdapter emojiPagerAdapter = new StickerPagerAdapter(mActivity, new ArrayList<StickerGroup>(emojiGroups), new StickerSelectListener() { @Override public void onStickerSelected(Sticker s) { sendStickerCode(s.assetUri); /** if (isGroupChat()) { sendStickerCode(s.assetUri); } else { mActivity.handleSendDelete(s.assetUri,"image/png", false, false, true); }*/ // mActivity.handleSendData(Uri.parse(s.assetPath),"image/png"); mViewAttach.setVisibility(View.INVISIBLE); showStickers(); } }); mStickerPager.setAdapter(emojiPagerAdapter); } } //generate a :pack-sticker: code private void sendStickerCode(Uri assetUri) { StringBuffer stickerCode = new StringBuffer(); stickerCode.append(":"); stickerCode.append(assetUri.getPathSegments().get(assetUri.getPathSegments().size() - 2)); stickerCode.append("-"); stickerCode.append(assetUri.getLastPathSegment().split("\\.")[0]); stickerCode.append(":"); sendMessageAsync(stickerCode.toString()); } void approveSubscription() { if (mConn != null) { try { IContactListManager manager = mConn.getContactListManager(); manager.approveSubscription(new Contact(new XmppAddress(mRemoteAddress), mRemoteNickname)); } catch (RemoteException e) { // mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "approve sub error", e); } } } void declineSubscription() { if (mConn != null) { try { IContactListManager manager = mConn.getContactListManager(); manager.declineSubscription(new Contact(new XmppAddress(mRemoteAddress), mRemoteNickname)); } catch (RemoteException e) { // mHandler.showServiceErrorAlert(e.getLocalizedMessage()); LogCleaner.error(ImApp.LOG_TAG, "decline sub error", e); } } } }