Java tutorial
/** * Copyright (C) 2014 Open Whisper Systems * <p/> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p/> * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * <p/> * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.thoughtcrime.securesms; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; import android.util.Log; import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.CheckBox; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.AlertDialogWrapper; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.components.PushRecipientsPanel; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.NotInDirectoryException; import org.thoughtcrime.securesms.database.TextSecureDirectory; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.ProfileImageTypeSelectorAdapter; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.textsecure.api.util.InvalidNumberException; import org.whispersystems.textsecure.internal.push.TextSecureProtos; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import de.gdata.messaging.util.GDataPreferences; import de.gdata.messaging.util.GService; import de.gdata.messaging.util.GUtil; import de.gdata.messaging.util.ProfileAccessor; import ws.com.google.android.mms.MmsException; public class ProfileFragment extends Fragment { private static final int PADDING_TOP = 0; private MasterSecret masterSecret; private GDataPreferences gDataPreferences; private String profileId = ""; private String phonenumber = ""; private static final int PICK_IMAGE = 1; private static final int TAKE_PHOTO = 2; private String profileStatusString = ""; private AutoCompleteTextView profileStatus; private ImageView xCloseButton; private ImageView phoneCall; private TextView imageText; private TextView statusDate; private TextView profilePhone; private ThumbnailView profilePicture; private Recipient recipient; private ScrollView scrollView; private LinearLayout historyLayout; private boolean hasChanged = false; private boolean isGroup; private Recipients recipients; private ListView groupMember; private Set<Recipient> selectedContacts; private Set<Recipient> existingContacts = null; private ProfileImageTypeSelectorAdapter attachmentAdapter; private static final int GROUP_EDIT = 5; private byte[] groupId; private RelativeLayout layout_status; private RelativeLayout layout_phone; private RelativeLayout layout_group; private boolean hasLeft = false; private HorizontalScrollView historyScrollView; private TextView historyContentTextView; private RelativeLayout historyLine; private TextView profileHeader; private PushRecipientsPanel recipientsPanel; private boolean keyboardIsVisible = false; private boolean contactsHaveChanged = false; private Button leaveGroup; private long threadId = -1; private int heightMemberList = 0; private ViewTreeObserver.OnScrollChangedListener onScrollChangeListener; private SeekBar seekBarFont; private FloatingActionButton floatingActionColorButton; private RelativeLayout layoutColor; private CheckBox chatPartnersColor; private CheckBox colorDefault; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { return GUtil.setFontForFragment(getActivity(), inflater.inflate(R.layout.profile_fragment, container, false)); } @Override public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); initializeResources(); refreshLayout(); this.getView().setFocusableInTouchMode(true); this.getView().setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { finishAndSave(); return true; } return false; } }); } private void refreshLayout() { gDataPreferences = new GDataPreferences(getActivity()); boolean isMyProfile = (GUtil.numberToLong(gDataPreferences.getE164Number() + "") + "") .contains(GUtil.numberToLong(profileId) + ""); layout_status = (RelativeLayout) getView().findViewById(R.id.layout_status); layout_phone = (RelativeLayout) getView().findViewById(R.id.layout_phone); layout_group = (RelativeLayout) getView().findViewById(R.id.layout_member); statusDate = (TextView) getView().findViewById(R.id.profile__date); leaveGroup = (Button) getView().findViewById(R.id.buttonLeaveGroup); profileHeader = (TextView) getView().findViewById(R.id.profile_header); profileStatus = (AutoCompleteTextView) getView().findViewById(R.id.profile_status); xCloseButton = (ImageView) getView().findViewById(R.id.profile_close); imageText = (TextView) getView().findViewById(R.id.image_text); profilePhone = (TextView) getView().findViewById(R.id.profile_phone); groupMember = (ListView) getView().findViewById(R.id.selected_contacts_list); historyLayout = (LinearLayout) getView().findViewById(R.id.historylayout); historyContentTextView = (TextView) getView().findViewById(R.id.history_content); historyScrollView = (HorizontalScrollView) getView().findViewById(R.id.horizontal_scroll); recipientsPanel = (PushRecipientsPanel) getView().findViewById(R.id.recipients); profilePhone.setText(phonenumber); profilePicture = (ThumbnailView) getView().findViewById(R.id.profile_picture); phoneCall = (ImageView) getView().findViewById(R.id.phone_call); (getView().findViewById(R.id.contacts_button)).setOnClickListener(new AddRecipientButtonListener()); historyLine = (RelativeLayout) getView().findViewById(R.id.layout_history); recipient = recipients.getPrimaryRecipient(); attachmentAdapter = new ProfileImageTypeSelectorAdapter(getActivity()); scrollView = (ScrollView) getView().findViewById(R.id.scrollView); seekBarFont = (SeekBar) getView().findViewById(R.id.seekbar_font); chatPartnersColor = (CheckBox) getView().findViewById(R.id.enabled_chat_partners_color); colorDefault = (CheckBox) getView().findViewById(R.id.color_default); layoutColor = (RelativeLayout) getView().findViewById(R.id.layout_color); floatingActionColorButton = (FloatingActionButton) getView().findViewById(R.id.fab_new_color); final ImageView profileStatusEdit = (ImageView) getView().findViewById(R.id.profile_status_edit); if (!isGroup) { ImageSlide slide = ProfileAccessor.getProfileAsImageSlide(getActivity(), masterSecret, profileId); if (slide != null && !isMyProfile) { if (masterSecret != null) { try { profilePicture.setImageResource(slide, masterSecret); } catch (IllegalStateException e) { Log.w("GDATA", "Unable to load profile image"); } profileStatus.setText(ProfileAccessor.getProfileStatusForRecepient(getActivity(), profileId), TextView.BufferType.EDITABLE); profileStatus.setEnabled(false); layout_status.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { profileStatusEdit.performClick(); return false; } }); statusDate.setText(GUtil.getLocalDate( ProfileAccessor.getProfileUpdateTimeForRecepient(getActivity(), profileId), getActivity().getApplicationContext())); imageText.setText(recipient.getName()); } profilePicture.setThumbnailClickListener(new ThumbnailClickListener()); } else if (ProfileAccessor.getMyProfilePicture(getActivity()).hasImage() && isMyProfile) { profileStatus.setText(ProfileAccessor.getProfileStatus(getActivity()), TextView.BufferType.EDITABLE); imageText.setText(getString(R.string.MediaPreviewActivity_you)); initColorSeekbar(); profilePicture.setThumbnailClickListener(new ThumbnailClickListener()); if ((ProfileAccessor.getMyProfilePicture(getActivity()).getUri() + "").equals("")) { profilePicture.setImageBitmap(ContactPhotoFactory.getDefaultContactPhoto(getActivity())); } else { profilePicture.setImageResource(ProfileAccessor.getMyProfilePicture(getActivity())); } historyLine.setVisibility(View.GONE); } else { imageText.setText(recipient.getName()); profilePicture.setImageBitmap(recipient.getContactPhoto()); } layout_group.setVisibility(View.GONE); } else { String groupName = recipient.getName(); Bitmap avatar = recipient.getContactPhoto(); String encodedGroupId = recipient.getNumber(); if (encodedGroupId != null) { try { groupId = GroupUtil.getDecodedId(encodedGroupId); } catch (IOException ioe) { groupId = null; } } GroupDatabase db = DatabaseFactory.getGroupDatabase(getActivity()); Recipients recipients = db.getGroupMembers(groupId, false); recipientsPanel.setPanelChangeListener(new PushRecipientsPanel.RecipientsPanelChangedListener() { @Override public void onRecipientsPanelUpdate(Recipients recipients) { Log.w("GDATA", "onRecipientsPanelUpdate received."); if (recipients != null) { addAllSelectedContacts(recipients.getRecipientsList()); syncAdapterWithSelectedContacts(); } } }); if (recipients != null) { final List<Recipient> recipientList = recipients.getRecipientsList(); if (recipientList != null) { if (existingContacts == null) existingContacts = new HashSet<>(recipientList.size()); existingContacts.addAll(recipientList); } if (recipientList != null) { if (existingContacts == null) existingContacts = new HashSet<>(recipientList.size()); existingContacts.addAll(recipientList); } SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(getActivity(), android.R.id.text1, new ArrayList<SelectedRecipientsAdapter.RecipientWrapper>()); adapter.clear(); if (existingContacts != null) { for (Recipient contact : existingContacts) { adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, false)); } } adapter.setMasterSecret(masterSecret); adapter.setThreadId(threadId); adapter.setOnRecipientDeletedListener(new SelectedRecipientsAdapter.OnRecipientDeletedListener() { @Override public void onRecipientDeleted(Recipient recipient) { removeSelectedContact(recipient); } }); groupMember.setAdapter(adapter); adapter.notifyDataSetChanged(); } if (avatar != null) { profilePicture.setVisibility(View.GONE); getView().findViewById(R.id.profile_picture_group).setVisibility(View.VISIBLE); scaleImage((ImageView) getView().findViewById(R.id.profile_picture_group), avatar); } imageText.setText(groupName); if (profileStatusString.equals("")) { profileStatus.setText(groupName); } layout_phone.setVisibility(View.GONE); leaveGroup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (isActiveGroup()) { handleLeavePushGroup(); } else { handleDeleteThread(); } } }); if (!isActiveGroup()) { leaveGroup.setText(getString(R.string.conversation__menu_delete_thread)); } heightMemberList = GUtil.setListViewHeightBasedOnChildren(groupMember); } ImageView profileImageEdit = (ImageView) getView().findViewById(R.id.profile_picture_edit); ImageView profileImageDelete = (ImageView) getView().findViewById(R.id.profile_picture_delete); if (!isMyProfile && !isGroup) { profileStatusEdit.setVisibility(View.GONE); profileImageDelete.setVisibility(View.GONE); profileImageEdit.setVisibility(View.GONE); } else { if (isGroup) { profileImageDelete.setVisibility(View.GONE); profileHeader.setText(getString(R.string.group_title)); } else { profileImageDelete.setVisibility(View.VISIBLE); } profileStatusEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { profileStatus.setEnabled(!profileStatus.isEnabled()); if (!profileStatus.isEnabled()) { hasChanged = true; hasLeft = false; profileStatusEdit.setImageDrawable(getResources().getDrawable(R.drawable.ic_content_edit)); InputMethodManager imm = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(profileStatus.getWindowToken(), 0); if (isGroup) { new UpdateWhisperGroupAsyncTask().execute(); } else { ProfileAccessor.setProfileStatus(getActivity(), profileStatus.getText() + ""); } } else { profileStatusEdit .setImageDrawable(getResources().getDrawable(R.drawable.ic_send_sms_gdata)); profileStatus.showDropDown(); profileStatus.requestFocus(); } } }); profileImageDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { hasChanged = true; hasLeft = false; ProfileAccessor.deleteMyProfilePicture(getActivity()); refreshLayout(); profileStatus.dismissDropDown(); gDataPreferences.hasProfileImageChanged(true); } }); profileImageEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { gDataPreferences.hasProfileImageChanged(true); hasChanged = true; hasLeft = false; handleAddAttachment(); } }); } if (isMyProfile) { ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_dropdown_item_1line, getResources().getStringArray(R.array.status_suggestions)); profileStatus.setAdapter(adapter); profileStatus.setCompletionHint(getString(R.string.status_hint)); profileStatus.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { profileStatus.dismissDropDown(); } }); profileStatus.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { profileStatus.setEnabled(!profileStatus.isEnabled()); if (!profileStatus.isEnabled()) { hasChanged = true; hasLeft = false; profileStatusEdit.setImageDrawable(getResources().getDrawable(R.drawable.ic_content_edit)); InputMethodManager imm = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(profileStatus.getWindowToken(), 0); if (isGroup) { new UpdateWhisperGroupAsyncTask().execute(); } else { ProfileAccessor.setProfileStatus(getActivity(), profileStatus.getText() + ""); } } } }); } else { // if(!isMyProfile) { setMediaHistoryImages(); } xCloseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { finishAndSave(); } }); phoneCall.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { handleDial(recipient); } }); final RelativeLayout scrollContainer = (RelativeLayout) getView().findViewById(R.id.scrollContainer); final LinearLayout mainLayout = (LinearLayout) getView().findViewById(R.id.mainlayout); scrollView.setSmoothScrollingEnabled(true); ViewTreeObserver vto = scrollView.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { scrollView.scrollTo(0, mainLayout.getTop() - PADDING_TOP); } }); scrollContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // getActivity().finish(); } }); onScrollChangeListener = new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { if (BuildConfig.VERSION_CODE >= 11) { scrollContainer.setBackgroundColor(Color.WHITE); scrollContainer .setAlpha((float) ((1000.0 / scrollContainer.getHeight()) * scrollView.getHeight())); } int keyboardHeight = 150; int paddingBottom = 250; int scrollViewHeight = scrollView.getHeight(); if (getActivity().getResources() .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { scrollViewHeight = 2 * scrollViewHeight; } int heightDiff = scrollView.getRootView().getHeight() - scrollView.getHeight(); if (pxToDp(heightDiff) > keyboardHeight) { keyboardIsVisible = true; } else { keyboardIsVisible = false; } if (!keyboardIsVisible) { if ((mainLayout.getTop() - scrollViewHeight) > scrollView.getScrollY() - pxToDp(paddingBottom)) { finishAndSave(); } if ((scrollViewHeight + (heightMemberList)) < (scrollView.getScrollY() - mainLayout.getTop())) { finishAndSave(); } } } }; scrollView.getViewTreeObserver().addOnScrollChangedListener(onScrollChangeListener); scrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { ViewTreeObserver observer = scrollView.getViewTreeObserver(); observer.addOnScrollChangedListener(onScrollChangeListener); return false; } }); } private void handleLeavePushGroup() { if (recipients == null) { Toast.makeText(getActivity(), getString(R.string.ConversationActivity_invalid_recipient), Toast.LENGTH_LONG).show(); return; } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(getString(R.string.ConversationActivity_leave_group)); builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_info_icon)); builder.setCancelable(true); builder.setMessage(getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group)); AlertDialog.Builder builder1 = builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Context self = getActivity(); try { byte[] groupId = GroupUtil.getDecodedId(recipients.getPrimaryRecipient().getNumber()); DatabaseFactory.getGroupDatabase(self).setActive(groupId, false); TextSecureProtos.GroupContext context = TextSecureProtos.GroupContext.newBuilder() .setId(ByteString.copyFrom(groupId)) .setType(TextSecureProtos.GroupContext.Type.QUIT).build(); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(self, recipients, context, null); MessageSender.send(self, masterSecret, outgoingMessage, threadId, false); DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self)); } catch (IOException e) { Toast.makeText(self, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); } getActivity().finish(); } }); builder.setNegativeButton(R.string.no, null); builder.show(); } private boolean isActiveGroup() { try { byte[] groupId = GroupUtil.getDecodedId(recipients.getPrimaryRecipient().getNumber()); GroupDatabase.GroupRecord record = DatabaseFactory.getGroupDatabase(getActivity()).getGroup(groupId); return record != null && record.isActive(); } catch (IOException e) { Log.w("ConversationActivity", e); return false; } } private void handleDeleteThread() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.ConversationActivity_delete_thread_confirmation); builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon)); builder.setCancelable(true); builder.setMessage( R.string.ConversationActivity_are_you_sure_that_you_want_to_permanently_delete_this_conversation_question); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (threadId > 0) { DatabaseFactory.getThreadDatabase(getActivity()).deleteConversation(threadId); getActivity().finish(); } } }); builder.setNegativeButton(R.string.no, null); builder.show(); } private class AddRecipientButtonListener implements View.OnClickListener { @Override public void onClick(View v) { contactsHaveChanged = true; hasLeft = false; Intent intent = new Intent(getActivity(), PushContactSelectionActivity.class); if (existingContacts != null) intent.putExtra(PushContactSelectionActivity.PUSH_ONLY_EXTRA, true); startActivityForResult(intent, PICK_CONTACT); } } private static final int PICK_CONTACT = 1; @Override public void onActivityResult(int reqCode, int resultCode, Intent data) { switch (reqCode) { case PICK_CONTACT: if (data == null || resultCode != Activity.RESULT_OK) return; List<ContactAccessor.ContactData> selected = data.getParcelableArrayListExtra("contacts"); for (ContactAccessor.ContactData contact : selected) { for (ContactAccessor.NumberData numberData : contact.numbers) { Recipient recipient = RecipientFactory .getRecipientsFromString(getActivity(), numberData.number, false).getPrimaryRecipient(); if (!selectedContacts.contains(recipient) && (existingContacts == null || !existingContacts.contains(recipient))) { addSelectedContact(recipient); } } } syncAdapterWithSelectedContacts(); break; } } private void initColorSeekbar() { layoutColor.setVisibility(View.VISIBLE); chatPartnersColor.setChecked(gDataPreferences.getChatPartnersColorEnabled()); chatPartnersColor.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { gDataPreferences.saveChatPartnersColorEnabled(chatPartnersColor.isChecked()); } }); colorDefault.setChecked(gDataPreferences.getColorDefaultEnabled()); seekBarFont.setEnabled(!gDataPreferences.getColorDefaultEnabled()); colorDefault.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { gDataPreferences.setColorDefaultEnabled(colorDefault.isChecked()); seekBarFont.setEnabled(!gDataPreferences.getColorDefaultEnabled()); floatingActionColorButton.setRippleColor(gDataPreferences.getCurrentColorHex()); hasChanged = true; } }); final int maxValueColorMOne = 255; final int maxValueColor = 256; seekBarFont.setMax(maxValueColor * 6 - (maxValueColor / 6)); int oldColor = gDataPreferences.getCurrentColorHex(); seekBarFont.setProgress(gDataPreferences.getColorProgress()); floatingActionColorButton.setRippleColor(oldColor); seekBarFont.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { int color = Color.BLUE; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { int r = 0; int g = 0; int b = 0; if (progress < maxValueColor) { b = progress; } else if (progress < maxValueColor * 2) { g = progress % maxValueColor; b = maxValueColor - progress % maxValueColor; } else if (progress < maxValueColor * 3) { g = maxValueColorMOne; b = progress % maxValueColor; } else if (progress < maxValueColor * 4) { r = progress % maxValueColor; g = maxValueColor - progress % maxValueColor; b = maxValueColor - progress % maxValueColor; } else if (progress < maxValueColor * 5) { r = maxValueColorMOne; g = 0; b = progress % maxValueColor; } else if (progress < maxValueColor * 6) { r = maxValueColorMOne; g = progress % maxValueColor; b = maxValueColor - progress % maxValueColor; } else if (progress < maxValueColor * 7) { r = maxValueColorMOne; g = maxValueColorMOne; b = progress % maxValueColor; } color = Color.argb(maxValueColorMOne, r, g, b); floatingActionColorButton.setRippleColor(color); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { gDataPreferences.saveCurrentColorValue(color); gDataPreferences.saveCurrentSeekBarColorProgress(seekBar.getProgress()); hasChanged = true; } }); } /** * An incoming video media has an attached second image attachment used as preview for the encrypted video file. * To detect whether is is a video, we check the next attachment for the same message id and use this as the preview for the encrypted video. * To detect whether it is just the second of a video, we check for the same message id as the previous attachment. If yes, we hide it. * Not beautiful, but working... */ private void setMediaHistoryImages() { String[] mediaHistoryUris = gDataPreferences .getMediaUriHistoryForId(GUtil.numberToLong(recipient.getNumber())); String[] mediaHistoryIds = gDataPreferences .getMediaMessageIdHistoryForContactId(GUtil.numberToLong(recipient.getNumber())); String lastMessageId = "noLastMessageId"; String nextMessageId = "nonextMessageId"; String currentMessageId = "nocurrentMessageId"; if (mediaHistoryUris.length > 0) { while (historyLayout.getChildCount() >= 1) { historyLayout.removeView(historyLayout.getChildAt(0)); } for (int i = 0; i < mediaHistoryUris.length; i++) { boolean isVideo = false; boolean wasVideo = false; nextMessageId = mediaHistoryIds.length > i + 1 ? mediaHistoryIds[i + 1] : "nonextMessageId"; currentMessageId = mediaHistoryIds.length > i ? mediaHistoryIds[i] : "nocurrentMessageId"; lastMessageId = i - 1 >= 0 ? mediaHistoryIds[i - 1] : "noLastMessageId"; if (nextMessageId.equals(currentMessageId)) { isVideo = true; } if (currentMessageId.equals(lastMessageId)) { wasVideo = true; } Slide mediaHistorySlide = ProfileAccessor.getSlideForUri(getActivity(), masterSecret, mediaHistoryUris[i], false); VideoSlide videoSlide = null; if (mediaHistoryIds.length > i + 1 && isVideo && !wasVideo) { Slide nextSlide = ProfileAccessor.getSlideForUri(getActivity(), masterSecret, mediaHistoryUris[i + 1], true); videoSlide = nextSlide instanceof VideoSlide ? (VideoSlide) nextSlide : null; } if (mediaHistorySlide != null && masterSecret != null && !(mediaHistorySlide.getUri() + "").equals("") && !wasVideo) { ThumbnailView historyMedia = new ThumbnailView(getActivity()); android.widget.LinearLayout.LayoutParams layoutParams = new android.widget.LinearLayout.LayoutParams( dpToPx(100), dpToPx(100)); historyMedia .setBackgroundColor(getResources().getColor(R.color.conversation_list_divider_light)); layoutParams.setMargins(5, 0, 5, 0); historyMedia.setLayoutParams(layoutParams); historyMedia.setSlide(mediaHistorySlide); historyMedia.setVideoSlide(videoSlide); ProfileAccessor.buildEncryptedPartGlideRequest(mediaHistorySlide, masterSecret, getActivity()) .into(historyMedia); historyLayout.addView(historyMedia); } } LinearLayout ll = ((LinearLayout) historyScrollView.getChildAt(0)); if (ll.getChildCount() > 0 && historyContentTextView != null && mediaHistoryUris != null && mediaHistoryUris.length > 0) { historyContentTextView.setVisibility(View.GONE); } for (int l = 0; l < ll.getChildCount(); l++) { ((ThumbnailView) ll.getChildAt(l)).setOnClickListener(mediaOnClickListener); } } } View.OnClickListener mediaOnClickListener = new View.OnClickListener() { @Override public void onClick(View view) { final Slide slide = ((ThumbnailView) view).getSlide(); final VideoSlide vidSlide = (VideoSlide) ((ThumbnailView) view).getVideoSlide(); if (slide != null && MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && vidSlide == null) { Intent intent = new Intent(getActivity(), MediaPreviewActivity.class); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(slide.getUri(), slide.getContentType()); intent.putExtra(MediaPreviewActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipient.getRecipientId()); intent.putExtra(MediaPreviewActivity.DATE_EXTRA, System.currentTimeMillis()); getActivity().startActivity(intent); } else { AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity()); builder.setTitle(R.string.ConversationItem_view_secure_media_question); builder.setIconAttribute(R.attr.dialog_alert_icon); builder.setCancelable(true); builder.setMessage( R.string.ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { fireIntent(vidSlide); } }); builder.setNegativeButton(R.string.no, null); builder.show(); } } }; private void fireIntent(Slide slide) { if (slide != null) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(PartAuthority.getPublicPartUri(slide.getUri()), slide.getContentType()); try { getActivity().startActivity(intent); } catch (ActivityNotFoundException anfe) { Log.w("GDATA", anfe.getMessage() + " - " + slide.getContentType()); } } } private void addAllSelectedContacts(Collection<Recipient> contacts) { for (Recipient contact : contacts) { addSelectedContact(contact); } } private void removeSelectedContact(Recipient contact) { selectedContacts.remove(contact); if (!isActiveInDirectory(getActivity(), contact)) { for (Recipient recipient : selectedContacts) { if (!isActiveInDirectory(getActivity(), recipient)) return; } } } private void syncAdapterWithSelectedContacts() { SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter) groupMember.getAdapter(); adapter.clear(); for (Recipient contact : selectedContacts) { adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, true)); } if (existingContacts != null) { for (Recipient contact : existingContacts) { adapter.add(new SelectedRecipientsAdapter.RecipientWrapper(contact, false)); } } adapter.notifyDataSetChanged(); heightMemberList = GUtil.setListViewHeightBasedOnChildren(groupMember); } private void addSelectedContact(Recipient contact) { final boolean isPushUser = isActiveInDirectory(getActivity(), contact); if (existingContacts != null && !isPushUser) { Toast.makeText(getActivity(), R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group, Toast.LENGTH_LONG).show(); return; } if (!selectedContacts.contains(contact) && (existingContacts == null || !existingContacts.contains(contact))) selectedContacts.add(contact); contactsHaveChanged = true; } private static boolean isActiveInDirectory(Context context, Recipient recipient) { try { if (!TextSecureDirectory.getInstance(context) .isActiveNumber(Util.canonicalizeNumber(context, recipient.getNumber()))) { return false; } } catch (NotInDirectoryException e) { return false; } catch (InvalidNumberException e) { return false; } return true; } private void finishAndSave() { hasLeft = true; if (hasChanged || contactsHaveChanged) { if (isGroup) { new UpdateWhisperGroupAsyncTask().execute(); } else { ProfileAccessor.sendProfileUpdateToAllContacts(getActivity(), masterSecret); } hasChanged = false; contactsHaveChanged = false; } getActivity().finish(); } private int dpToPx(int dp) { float density = getActivity().getResources().getDisplayMetrics().density; return Math.round((float) dp * density); } private int pxToDp(int px) { float density = getActivity().getResources().getDisplayMetrics().density; return Math.round((float) px / density); } private class AttachmentTypeListener implements DialogInterface.OnClickListener { @Override public void onClick(DialogInterface dialog, int which) { addAttachment(attachmentAdapter.buttonToCommand(which)); } } private void handleAddAttachment() { AlertDialog.Builder builder = new AlertDialog.Builder( new ContextThemeWrapper(getActivity(), R.style.GSecure_Light_Dialog)); builder.setIcon(R.drawable.ic_dialog_attach); builder.setTitle(R.string.profile_select_picture); builder.setAdapter(attachmentAdapter, new AttachmentTypeListener()); builder.show(); } private void addAttachment(int type) { Log.w("ComposeMessageActivity", "Selected: " + type); switch (type) { case AttachmentTypeSelectorAdapter.ADD_IMAGE: AttachmentManager.selectImage(getActivity(), PICK_IMAGE); break; case AttachmentTypeSelectorAdapter.TAKE_PHOTO: AttachmentManager.takePhoto(getActivity(), TAKE_PHOTO); break; } } private void handleDial(Recipient recipient) { try { if (recipient == null) return; Intent dialIntent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + recipient.getNumber())); startActivity(dialIntent); } catch (ActivityNotFoundException anfe) { Dialogs.showAlertDialog(getActivity(), getString(R.string.ConversationActivity_calls_not_supported), getString(R.string.ConversationActivity_this_device_does_not_appear_to_support_dial_actions)); } } @Override public void onResume() { if (hasChanged) { if (isGroup && !contactsHaveChanged) { new UpdateWhisperGroupAsyncTask().execute(); hasChanged = false; new FillExistingGroupInfoAsyncTask().execute(); } refreshLayout(); } super.onResume(); } @Override public void onLowMemory() { super.onLowMemory(); if (profilePicture != null) { if (profilePicture.getForeground() != null) { if (profilePicture.getForeground() instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) profilePicture.getForeground(); Bitmap bitmap = bitmapDrawable.getBitmap(); bitmap.recycle(); } } profilePicture.setImageBitmap(null); profilePicture.setForeground(null); profilePicture.setBackground(null); System.gc(); } } public static final String RECIPIENTS_EXTRA = "recipients"; @Override public void onAttach(Activity activity) { super.onAttach(activity); } private void initializeResources() { this.masterSecret = getActivity().getIntent().getParcelableExtra("master_secret"); this.phonenumber = getActivity().getIntent().getStringExtra("profile_id"); this.profileId = GUtil.numberToLong(phonenumber) + ""; this.isGroup = getActivity().getIntent().getBooleanExtra("is_group", false); threadId = getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); this.recipients = RecipientFactory.getRecipientsForIds(getActivity(), getActivity().getIntent().getLongArrayExtra(RECIPIENTS_EXTRA), true); selectedContacts = new HashSet<Recipient>(); } @Override public void onPause() { super.onPause(); if (hasChanged && hasLeft && !isGroup) { ProfileAccessor.sendProfileUpdateToAllContacts(getActivity(), masterSecret); hasChanged = false; } if (hasChanged && hasLeft && isGroup) { new UpdateWhisperGroupAsyncTask().execute(); hasChanged = false; contactsHaveChanged = false; } } private class UpdateWhisperGroupAsyncTask extends AsyncTask<Void, Void, Pair<Long, Recipients>> { private long RES_BAD_NUMBER = -2; private long RES_MMS_EXCEPTION = -3; @Override protected Pair<Long, Recipients> doInBackground(Void... params) { byte[] avatarBytes = null; final Bitmap bitmap; if (ProfileActivity.getAvatarTemp() == null) { final GroupDatabase db = DatabaseFactory.getGroupDatabase(getActivity()); GroupDatabase.GroupRecord group = db.getGroup(groupId); bitmap = getExistingBitmapForGroup(group); } else { bitmap = ProfileActivity.getAvatarTemp(); } if (bitmap != null) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); avatarBytes = stream.toByteArray(); } try { Set<Recipient> unionContacts = new HashSet<Recipient>(selectedContacts); unionContacts.addAll(existingContacts); return handleUpdatePushGroup(groupId, profileStatusString, avatarBytes, unionContacts); } catch (MmsException e) { Log.w("GDATA", e); return new Pair<Long, Recipients>(RES_MMS_EXCEPTION, null); } catch (InvalidNumberException e) { Log.w("GDATA", e); return new Pair<Long, Recipients>(RES_BAD_NUMBER, null); } } @Override protected void onPreExecute() { super.onPreExecute(); profileStatusString = (profileStatus != null && profileStatus.getText() != null) ? profileStatus.getText().toString() : ""; } @Override protected void onPostExecute(Pair<Long, Recipients> groupInfo) { final long threadId = groupInfo.first; if (threadId == RES_BAD_NUMBER) { Toast.makeText(getActivity(), R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show(); } else if (threadId == RES_MMS_EXCEPTION) { Toast.makeText(getActivity(), R.string.GroupCreateActivity_contacts_mms_exception, Toast.LENGTH_LONG).show(); } } } public static final String MASTER_SECRET_EXTRA = "master_secret"; private Pair<Long, Recipients> handleUpdatePushGroup(byte[] groupId, String groupName, byte[] avatar, Set<Recipient> members) throws InvalidNumberException, MmsException { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(GService.appContext); Set<String> memberE164Numbers = getE164Numbers(members); memberE164Numbers.add(TextSecurePreferences.getLocalNumber(GService.appContext)); for (String number : memberE164Numbers) Log.w("GDATA", "Updating: " + number); groupDatabase.updateMembers(groupId, new LinkedList<String>(memberE164Numbers)); groupDatabase.updateTitle(groupId, groupName); groupDatabase.updateAvatar(groupId, avatar); return handlePushOperation(groupId, groupName, avatar, memberE164Numbers); } private Pair<Long, Recipients> handlePushOperation(byte[] groupId, String groupName, byte[] avatar, Set<String> e164numbers) throws InvalidNumberException { String groupRecipientId = GroupUtil.getEncodedId(groupId); Recipients groupRecipient = RecipientFactory.getRecipientsFromString(GService.appContext, groupRecipientId, false); TextSecureProtos.GroupContext context = TextSecureProtos.GroupContext.newBuilder() .setId(ByteString.copyFrom(groupId)).setType(TextSecureProtos.GroupContext.Type.UPDATE) .setName(groupName).addAllMembers(e164numbers).build(); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(GService.appContext, groupRecipient, context, avatar); long threadId = MessageSender.send(GService.appContext, masterSecret, outgoingMessage, -1, false); return new Pair<>(threadId, groupRecipient); } private Set<String> getE164Numbers(Set<Recipient> recipients) throws InvalidNumberException { Set<String> results = new HashSet<String>(); for (Recipient recipient : recipients) { results.add(Util.canonicalizeNumber(GService.appContext, recipient.getNumber())); } return results; } @Override public void onDestroy() { super.onDestroy(); finishAndSave(); } private class ThumbnailClickListener implements ThumbnailView.ThumbnailClickListener { private void fireIntent(Slide slide) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(PartAuthority.getPublicPartUri(slide.getUri()), slide.getContentType()); intent.putExtra("destroyImage", true); try { getActivity().startActivity(intent); } catch (ActivityNotFoundException anfe) { Toast.makeText(getActivity(), R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG) .show(); } } public void onClick(final View v, final Slide slide) { if (slide != null) { if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType())) { Intent intent = new Intent(getActivity(), MediaPreviewActivity.class); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(slide.getUri(), slide.getContentType()); intent.putExtra(MediaPreviewActivity.MASTER_SECRET_EXTRA, masterSecret); Recipient primaryRecipient = RecipientFactory .getRecipientsFromString(getActivity(), String.valueOf(profileId), false) .getPrimaryRecipient(); if (primaryRecipient != null) { intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, primaryRecipient.getRecipientId()); } intent.putExtra("destroyImage", true); getActivity().startActivity(intent); } else { AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity()); builder.setTitle(R.string.ConversationItem_view_secure_media_question); builder.setIconAttribute(R.attr.dialog_alert_icon); builder.setCancelable(true); builder.setMessage( R.string.ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { fireIntent(slide); } }); builder.setNegativeButton(R.string.no, null); builder.show(); } } } } public Bitmap getExistingBitmapForGroup(GroupDatabase.GroupRecord group) { if (group != null) { final byte[] existingAvatar = group.getAvatar(); if (existingAvatar != null) { return BitmapFactory.decodeByteArray(existingAvatar, 0, existingAvatar.length); } } return null; } private class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask<Void, Void, Void> { private String existingTitle; private Bitmap existingAvatarBmp; public FillExistingGroupInfoAsyncTask() { super(getActivity(), R.string.GroupCreateActivity_loading_group_details, R.string.please_wait); } @Override protected Void doInBackground(Void... voids) { final GroupDatabase db = DatabaseFactory.getGroupDatabase(getActivity()); GroupDatabase.GroupRecord group = db.getGroup(groupId); final Recipients recipients = db.getGroupMembers(groupId, false); if (recipients != null) { final List<Recipient> recipientList = recipients.getRecipientsList(); if (recipientList != null) { if (existingContacts == null) existingContacts = new HashSet<>(recipientList.size()); existingContacts.addAll(recipientList); } } existingTitle = group.getTitle(); existingAvatarBmp = getExistingBitmapForGroup(group); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); if (existingTitle != null) { profileStatus.setText(existingTitle); imageText.setText(existingTitle); } if (existingAvatarBmp != null) { ProfileActivity.setAvatarTemp(existingAvatarBmp); profilePicture.setVisibility(View.GONE); scaleImage((ImageView) getView().findViewById(R.id.profile_picture_group), existingAvatarBmp); } } } private void scaleImage(ImageView view, Bitmap bitmap) { Bitmap scaledBitmap = scaleCenterCrop(bitmap, dpToPx(350), getActivity().getWindowManager().getDefaultDisplay().getWidth()); int width = scaledBitmap.getWidth(); int height = scaledBitmap.getHeight(); view.setImageDrawable(new BitmapDrawable(scaledBitmap)); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams(); params.width = width; params.height = height; view.setLayoutParams(params); } public Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) { int sourceWidth = source.getWidth(); int sourceHeight = source.getHeight(); float xScale = (float) newWidth / sourceWidth; float yScale = (float) newHeight / sourceHeight; float scale = Math.max(xScale, yScale); float scaledWidth = scale * sourceWidth; float scaledHeight = scale * sourceHeight; float left = (newWidth - scaledWidth) / 2; float top = (newHeight - scaledHeight) / 2; RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig()); Canvas canvas = new Canvas(dest); canvas.drawBitmap(source, null, targetRect, null); return dest; } }