mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 10:35:19 +00:00
513 lines
19 KiB
Java
513 lines
19 KiB
Java
package org.thoughtcrime.securesms;
|
|
|
|
|
|
import android.Manifest;
|
|
import android.animation.Animator;
|
|
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.Build;
|
|
import android.os.Bundle;
|
|
import android.provider.MediaStore;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.annotation.RequiresApi;
|
|
import android.text.Editable;
|
|
import android.text.TextUtils;
|
|
import android.text.TextWatcher;
|
|
import android.util.Log;
|
|
import android.view.KeyEvent;
|
|
import android.view.View;
|
|
import android.view.ViewAnimationUtils;
|
|
import android.view.WindowManager;
|
|
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.dd.CircularProgressButton;
|
|
import com.soundcloud.android.crop.Crop;
|
|
|
|
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
|
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
|
|
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
|
import org.thoughtcrime.securesms.database.Address;
|
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
|
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.profiles.SystemProfileUtil;
|
|
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.TextSecurePreferences;
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.security.SecureRandom;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.concurrent.ExecutionException;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import static android.provider.MediaStore.EXTRA_OUTPUT;
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
public class CreateProfileActivity extends BaseActionBarActivity implements InjectableType {
|
|
|
|
private static final String TAG = CreateProfileActivity.class.getSimpleName();
|
|
|
|
public static final String NEXT_INTENT = "next_intent";
|
|
public static final String EXCLUDE_SYSTEM = "exclude_system";
|
|
|
|
private static final int REQUEST_CODE_AVATAR = 1;
|
|
|
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
|
|
@Inject SignalServiceAccountManager accountManager;
|
|
|
|
private InputAwareLayout container;
|
|
private ImageView avatar;
|
|
private CircularProgressButton finishButton;
|
|
private EditText name;
|
|
private EmojiToggle emojiToggle;
|
|
private EmojiDrawer emojiDrawer;
|
|
private View reveal;
|
|
|
|
private Intent nextIntent;
|
|
private byte[] avatarBytes;
|
|
private File captureFile;
|
|
|
|
@Override
|
|
public void onCreate(Bundle bundle) {
|
|
super.onCreate(bundle);
|
|
|
|
dynamicTheme.onCreate(this);
|
|
dynamicLanguage.onCreate(this);
|
|
|
|
setContentView(R.layout.profile_create_activity);
|
|
|
|
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
|
getSupportActionBar().setTitle(R.string.CreateProfileActivity_your_profile_info);
|
|
|
|
initializeResources();
|
|
initializeEmojiInput();
|
|
initializeProfileName(getIntent().getBooleanExtra(EXCLUDE_SYSTEM, false));
|
|
initializeProfileAvatar(getIntent().getBooleanExtra(EXCLUDE_SYSTEM, false));
|
|
|
|
ApplicationContext.getInstance(this).injectDependencies(this);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
dynamicTheme.onResume(this);
|
|
dynamicLanguage.onResume(this);
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
if (container.isInputOpen()) container.hideCurrentInput(name);
|
|
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<Void, Void, byte[]>() {
|
|
@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.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
}.execute();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void initializeResources() {
|
|
TextView skipButton = ViewUtil.findById(this, R.id.skip_button);
|
|
TextView informationLabel = ViewUtil.findById(this, R.id.information_label);
|
|
|
|
this.avatar = ViewUtil.findById(this, R.id.avatar);
|
|
this.name = ViewUtil.findById(this, R.id.name);
|
|
this.emojiToggle = ViewUtil.findById(this, R.id.emoji_toggle);
|
|
this.emojiDrawer = ViewUtil.findById(this, R.id.emoji_drawer);
|
|
this.container = ViewUtil.findById(this, R.id.container);
|
|
this.finishButton = ViewUtil.findById(this, R.id.finish_button);
|
|
this.reveal = ViewUtil.findById(this, R.id.reveal);
|
|
this.nextIntent = getIntent().getParcelableExtra(NEXT_INTENT);
|
|
|
|
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());
|
|
|
|
this.name.addTextChangedListener(new TextWatcher() {
|
|
@Override
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
@Override
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
|
@Override
|
|
public void afterTextChanged(Editable s) {
|
|
if (s.toString().getBytes().length > ProfileCipher.NAME_PADDED_LENGTH) {
|
|
name.setError(getString(R.string.CreateProfileActivity_too_long));
|
|
finishButton.setEnabled(false);
|
|
} else if (name.getError() != null || !finishButton.isEnabled()) {
|
|
name.setError(null);
|
|
finishButton.setEnabled(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.finishButton.setOnClickListener(view -> {
|
|
this.finishButton.setIndeterminateProgressMode(true);
|
|
this.finishButton.setProgress(50);
|
|
handleUpload();
|
|
});
|
|
|
|
skipButton.setOnClickListener(view -> {
|
|
if (nextIntent != null) startActivity(nextIntent);
|
|
finish();
|
|
});
|
|
|
|
informationLabel.setOnClickListener(view -> {
|
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
intent.setData(Uri.parse("https://support.signal.org/hc/en-us/articles/115001434171"));
|
|
|
|
if (getPackageManager().queryIntentActivities(intent, 0).size() > 0) {
|
|
startActivity(intent);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void initializeProfileName(boolean excludeSystem) {
|
|
if (!TextUtils.isEmpty(TextSecurePreferences.getProfileName(this))) {
|
|
String profileName = TextSecurePreferences.getProfileName(this);
|
|
|
|
name.setText(profileName);
|
|
name.setSelection(profileName.length(), profileName.length());
|
|
} else if (!excludeSystem) {
|
|
SystemProfileUtil.getSystemProfileName(this).addListener(new ListenableFuture.Listener<String>() {
|
|
@Override
|
|
public void onSuccess(String result) {
|
|
if (!TextUtils.isEmpty(result)) {
|
|
name.setText(result);
|
|
name.setSelection(result.length(), result.length());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(ExecutionException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private void initializeProfileAvatar(boolean excludeSystem) {
|
|
Address ourAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this));
|
|
|
|
if (AvatarHelper.getAvatarFile(this, ourAddress).exists() && AvatarHelper.getAvatarFile(this, ourAddress).length() > 0) {
|
|
new AsyncTask<Void, Void, byte[]>() {
|
|
@Override
|
|
protected byte[] doInBackground(Void... params) {
|
|
try {
|
|
return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, ourAddress));
|
|
} 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);
|
|
} else if (!excludeSystem) {
|
|
SystemProfileUtil.getSystemProfileAvatar(this, new ProfileMediaConstraints()).addListener(new ListenableFuture.Listener<byte[]>() {
|
|
@Override
|
|
public void onSuccess(byte[] result) {
|
|
if (result != null) {
|
|
avatarBytes = result;
|
|
GlideApp.with(CreateProfileActivity.this)
|
|
.load(result)
|
|
.circleCrop()
|
|
.into(avatar);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(ExecutionException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private void initializeEmojiInput() {
|
|
this.emojiToggle.attach(emojiDrawer);
|
|
|
|
this.emojiToggle.setOnClickListener(v -> {
|
|
if (container.getCurrentInput() == emojiDrawer) {
|
|
container.showSoftkey(name);
|
|
} else {
|
|
container.show(name, emojiDrawer);
|
|
}
|
|
});
|
|
|
|
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.container.addOnKeyboardShownListener(() -> emojiToggle.setToEmoji());
|
|
this.name.setOnClickListener(v -> container.showSoftkey(name));
|
|
}
|
|
|
|
private Intent createAvatarSelectionIntent(@Nullable File captureFile, boolean includeClear, boolean includeCamera) {
|
|
List<Intent> 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.CreateProfileActivity_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;
|
|
final StreamDetails avatar;
|
|
|
|
if (TextUtils.isEmpty(this.name.getText().toString())) name = null;
|
|
else name = this.name.getText().toString();
|
|
|
|
if (avatarBytes == null || avatarBytes.length == 0) avatar = null;
|
|
else avatar = new StreamDetails(new ByteArrayInputStream(avatarBytes),
|
|
"image/jpeg", avatarBytes.length);
|
|
|
|
new AsyncTask<Void, Void, Boolean>() {
|
|
@Override
|
|
protected Boolean doInBackground(Void... params) {
|
|
Context context = CreateProfileActivity.this;
|
|
byte[] profileKey = ProfileKeyUtil.getProfileKey(CreateProfileActivity.this);
|
|
|
|
try {
|
|
accountManager.setProfileName(profileKey, name);
|
|
TextSecurePreferences.setProfileName(context, name);
|
|
} catch (IOException e) {
|
|
Log.w(TAG, e);
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
accountManager.setProfileAvatar(profileKey, avatar);
|
|
AvatarHelper.setAvatar(CreateProfileActivity.this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), avatarBytes);
|
|
TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt());
|
|
} catch (IOException e) {
|
|
Log.w(TAG, e);
|
|
return false;
|
|
}
|
|
|
|
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob(context));
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onPostExecute(Boolean result) {
|
|
super.onPostExecute(result);
|
|
|
|
if (result) {
|
|
if (captureFile != null) captureFile.delete();
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) handleFinishedLollipop();
|
|
else handleFinishedLegacy();
|
|
} else {
|
|
Toast.makeText(CreateProfileActivity.this, R.string.CreateProfileActivity_problem_setting_profile, Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
}
|
|
|
|
private void handleFinishedLegacy() {
|
|
finishButton.setProgress(0);
|
|
if (nextIntent != null) startActivity(nextIntent);
|
|
finish();
|
|
}
|
|
|
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
private void handleFinishedLollipop() {
|
|
int[] finishButtonLocation = new int[2];
|
|
int[] revealLocation = new int[2];
|
|
|
|
finishButton.getLocationInWindow(finishButtonLocation);
|
|
reveal.getLocationInWindow(revealLocation);
|
|
|
|
int finishX = finishButtonLocation[0] - revealLocation[0];
|
|
int finishY = finishButtonLocation[1] - revealLocation[1];
|
|
|
|
finishX += finishButton.getWidth() / 2;
|
|
finishY += finishButton.getHeight() / 2;
|
|
|
|
Animator animation = ViewAnimationUtils.createCircularReveal(reveal, finishX, finishY, 0f, (float) Math.max(reveal.getWidth(), reveal.getHeight()));
|
|
animation.setDuration(500);
|
|
animation.addListener(new Animator.AnimatorListener() {
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
finishButton.setProgress(0);
|
|
if (nextIntent != null) startActivity(nextIntent);
|
|
finish();
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationCancel(Animator animation) {}
|
|
@Override
|
|
public void onAnimationRepeat(Animator animation) {}
|
|
});
|
|
|
|
reveal.setVisibility(View.VISIBLE);
|
|
animation.start();
|
|
}
|
|
}
|