package org.thoughtcrime.securesms; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.MediaStore; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.textfield.TextInputEditText; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.soundcloud.android.crop.Crop; import org.thoughtcrime.securesms.components.InputAwareLayout; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.IntentUtils; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import java.io.File; import java.io.IOException; import java.security.SecureRandom; import java.util.LinkedList; import java.util.List; import static android.provider.MediaStore.EXTRA_OUTPUT; @SuppressLint("StaticFieldLeak") public class CreateProfileActivity extends BaseActionBarActivity { private static final String TAG = CreateProfileActivity.class.getSimpleName(); public static final String FROM_WELCOME = "from_welcome"; private static final int REQUEST_CODE_AVATAR = 1; private final DynamicTheme dynamicTheme = new DynamicTheme(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private InputAwareLayout container; private ImageView avatar; private EditText name; private EmojiDrawer emojiDrawer; private TextInputEditText statusView; private View reveal; private boolean fromWelcome; private byte[] avatarBytes; private File captureFile; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); this.fromWelcome = getIntent().getBooleanExtra(FROM_WELCOME, false); dynamicTheme.onCreate(this); dynamicLanguage.onCreate(this); setContentView(R.layout.profile_create_activity); getSupportActionBar().setTitle(R.string.pref_profile_info_headline); getSupportActionBar().setDisplayHomeAsUpEnabled(!this.fromWelcome); getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); initializeResources(); initializeEmojiInput(); initializeProfileName(); initializeProfileAvatar(); initializeStatusText(); } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuInflater inflater = this.getMenuInflater(); inflater.inflate(R.menu.preferences_create_profile_menu, menu); return true; } @Override public void onResume() { super.onResume(); dynamicTheme.onResume(this); dynamicLanguage.onResume(this); } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; case R.id.menu_create_profile: handleUpload(); break; } return false; } @Override public void onBackPressed() { if (container.isInputOpen()) { container.hideCurrentInput(name); } else if (fromWelcome) { startActivity(new Intent(getApplicationContext(), ConversationListActivity.class)); finish(); } else { super.onBackPressed(); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (container.getCurrentInput() == emojiDrawer) { container.hideAttachedInput(true); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_CODE_AVATAR: if (resultCode == Activity.RESULT_OK) { Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped")); Uri inputFile = (data != null ? data.getData() : null); if (inputFile == null && captureFile != null) { inputFile = Uri.fromFile(captureFile); } if (data != null && data.getBooleanExtra("delete", false)) { avatarBytes = null; avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400))); } else { new Crop(inputFile).output(outputFile).asSquare().start(this); } } break; case Crop.REQUEST_CROP: if (resultCode == Activity.RESULT_OK) { new AsyncTask() { @Override protected byte[] doInBackground(Void... params) { try { BitmapUtil.ScaleResult result = BitmapUtil.createScaledBytes(CreateProfileActivity.this, Crop.getOutput(data), new ProfileMediaConstraints()); return result.getBitmap(); } catch (BitmapDecodingException e) { Log.w(TAG, e); return null; } } @Override protected void onPostExecute(byte[] result) { if (result != null) { avatarBytes = result; GlideApp.with(CreateProfileActivity.this) .load(avatarBytes) .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) .circleCrop() .into(avatar); } else { Toast.makeText(CreateProfileActivity.this, R.string.error, Toast.LENGTH_LONG).show(); } } }.execute(); } break; } } private void initializeResources() { TextView passwordAccountSettings = ViewUtil.findById(this, R.id.password_account_settings_button); TextView loginSuccessText = ViewUtil.findById(this, R.id.login_success_text); this.avatar = ViewUtil.findById(this, R.id.avatar); this.name = ViewUtil.findById(this, R.id.name_text); this.emojiDrawer = ViewUtil.findById(this, R.id.emoji_drawer); this.container = ViewUtil.findById(this, R.id.container); this.reveal = ViewUtil.findById(this, R.id.reveal); this.statusView = ViewUtil.findById(this, R.id.status_text); this.avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400))); this.avatar.setOnClickListener(view -> Permissions.with(this) .request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) .ifNecessary() .onAnyResult(this::handleAvatarSelectionWithPermissions) .execute()); passwordAccountSettings.setOnClickListener(view -> { Intent intent = new Intent(this, RegistrationActivity.class); startActivity(intent); }); if (fromWelcome) { String addr = DcHelper.get(this, "addr"); loginSuccessText.setText(String.format( "Login successfull - your email address is %1$s\n\n" + "If you like, you can now enter a name and an avatar image " + "that will be displayed to people you write to.", addr)); ViewUtil.findById(this, R.id.status_text_layout).setVisibility(View.GONE); ViewUtil.findById(this, R.id.information_label).setVisibility(View.GONE); passwordAccountSettings.setVisibility(View.GONE); } else { loginSuccessText.setVisibility(View.GONE); } } private void initializeProfileName() { String profileName = DcHelper.get(this, DcHelper.CONFIG_DISPLAY_NAME); if (!TextUtils.isEmpty(profileName)) { name.setText(profileName); name.setSelection(profileName.length(), profileName.length()); } } private void initializeProfileAvatar() { String address = DcHelper.get(this, DcHelper.CONFIG_ADDRESS); if (AvatarHelper.getSelfAvatarFile(this, address).exists() && AvatarHelper.getSelfAvatarFile(this, address).length() > 0) { new AsyncTask() { @Override protected byte[] doInBackground(Void... params) { try { return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, address)); } catch (IOException e) { Log.w(TAG, e); return null; } } @Override protected void onPostExecute(byte[] result) { if (result != null) { avatarBytes = result; GlideApp.with(CreateProfileActivity.this) .load(result) .circleCrop() .into(avatar); } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } private void initializeEmojiInput() { this.emojiDrawer.setEmojiEventListener(new EmojiDrawer.EmojiEventListener() { @Override public void onKeyEvent(KeyEvent keyEvent) { name.dispatchKeyEvent(keyEvent); } @Override public void onEmojiSelected(String emoji) { final int start = name.getSelectionStart(); final int end = name.getSelectionEnd(); name.getText().replace(Math.min(start, end), Math.max(start, end), emoji); name.setSelection(start + emoji.length()); } }); this.name.setOnClickListener(v -> container.showSoftkey(name)); } private void initializeStatusText() { String status = DcHelper.get(this, DcHelper.CONFIG_SELF_STATUS); statusView.setText(status); } private Intent createAvatarSelectionIntent(@Nullable File captureFile, boolean includeClear, boolean includeCamera) { List extraIntents = new LinkedList<>(); Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI); galleryIntent.setType("image/*"); if (!IntentUtils.isResolvable(CreateProfileActivity.this, galleryIntent)) { galleryIntent = new Intent(Intent.ACTION_GET_CONTENT); galleryIntent.setType("image/*"); } if (includeCamera) { Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (captureFile != null && cameraIntent.resolveActivity(getPackageManager()) != null) { cameraIntent.putExtra(EXTRA_OUTPUT, FileProviderUtil.getUriFor(this, captureFile)); extraIntents.add(cameraIntent); } } if (includeClear) { extraIntents.add(new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO")); } Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.pref_profile_photo)); if (!extraIntents.isEmpty()) { chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0])); } return chooserIntent; } private void handleAvatarSelectionWithPermissions() { boolean hasCameraPermission = Permissions.hasAll(this, Manifest.permission.CAMERA); if (hasCameraPermission) { try { captureFile = File.createTempFile("capture", "jpg", getExternalCacheDir()); } catch (IOException e) { Log.w(TAG, e); captureFile = null; } } Intent chooserIntent = createAvatarSelectionIntent(captureFile, avatarBytes != null, hasCameraPermission); startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR); } private void handleUpload() { final String name; if (TextUtils.isEmpty(this.name.getText().toString())) name = null; else name = this.name.getText().toString(); new AsyncTask() { @Override protected Boolean doInBackground(Void... params) { Context context = CreateProfileActivity.this; DcHelper.set(context, DcHelper.CONFIG_DISPLAY_NAME, name); setStatusText(); try { AvatarHelper.setSelfAvatar(CreateProfileActivity.this, DcHelper.get(context, DcHelper.CONFIG_ADDRESS), avatarBytes); Prefs.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt()); } catch (IOException e) { Log.w(TAG, e); return false; } return true; } @Override public void onPostExecute(Boolean result) { super.onPostExecute(result); if (result) { if (captureFile != null) captureFile.delete(); if (fromWelcome) { startActivity(new Intent(getApplicationContext(), ConversationListActivity.class)); } finish(); } else { Toast.makeText(CreateProfileActivity.this, R.string.error, Toast.LENGTH_LONG).show(); } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private void setStatusText() { String newStatus = statusView.getText().toString().trim(); DcHelper.set(this, DcHelper.CONFIG_SELF_STATUS, newStatus); } }