summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/callcomposer/CallComposerActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/callcomposer/CallComposerActivity.java')
-rw-r--r--java/com/android/dialer/callcomposer/CallComposerActivity.java295
1 files changed, 202 insertions, 93 deletions
diff --git a/java/com/android/dialer/callcomposer/CallComposerActivity.java b/java/com/android/dialer/callcomposer/CallComposerActivity.java
index 074fc6de1..fa380cc19 100644
--- a/java/com/android/dialer/callcomposer/CallComposerActivity.java
+++ b/java/com/android/dialer/callcomposer/CallComposerActivity.java
@@ -23,51 +23,63 @@ import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
-import android.support.v4.view.ViewPager;
+import android.support.v4.util.Pair;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Base64;
+import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.View.OnLayoutChangeListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
-import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.ProgressBar;
import android.widget.QuickContactBadge;
import android.widget.RelativeLayout;
import android.widget.TextView;
-import com.android.contacts.common.ContactPhotoManager;
+import android.widget.Toast;
import com.android.dialer.callcomposer.CallComposerFragment.CallComposerListener;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.UiUtil;
+import com.android.dialer.common.concurrent.DialerExecutor;
import com.android.dialer.common.concurrent.DialerExecutors;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.configprovider.ConfigProviderBindings;
import com.android.dialer.constants.Constants;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.dialercontact.DialerContact;
import com.android.dialer.enrichedcall.EnrichedCallComponent;
import com.android.dialer.enrichedcall.EnrichedCallManager;
-import com.android.dialer.enrichedcall.EnrichedCallManager.State;
import com.android.dialer.enrichedcall.Session;
+import com.android.dialer.enrichedcall.Session.State;
import com.android.dialer.enrichedcall.extensions.StateExtension;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.multimedia.MultimediaData;
import com.android.dialer.protos.ProtoParsers;
import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.UriUtils;
import com.android.dialer.util.ViewUtil;
import com.android.dialer.widget.DialerToolbar;
+import com.android.dialer.widget.LockableViewPager;
+import com.android.incallui.callpending.CallPendingActivity;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.File;
@@ -86,10 +98,10 @@ public class CallComposerActivity extends AppCompatActivity
implements OnClickListener,
OnPageChangeListener,
CallComposerListener,
- OnLayoutChangeListener,
EnrichedCallManager.StateChangedListener {
public static final String KEY_CONTACT_NAME = "contact_name";
+ private static final String KEY_IS_FIRST_CALL_COMPOSE = "is_first_call_compose";
private static final int ENTRANCE_ANIMATION_DURATION_MILLIS = 500;
private static final int EXIT_ANIMATION_DURATION_MILLIS = 500;
@@ -98,11 +110,27 @@ public class CallComposerActivity extends AppCompatActivity
private static final String ARG_CALL_COMPOSER_CONTACT_BASE64 = "CALL_COMPOSER_CONTACT_BASE64";
private static final String ENTRANCE_ANIMATION_KEY = "entrance_animation_key";
+ private static final String SEND_AND_CALL_READY_KEY = "send_and_call_ready_key";
private static final String CURRENT_INDEX_KEY = "current_index_key";
private static final String VIEW_PAGER_STATE_KEY = "view_pager_state_key";
private static final String SESSION_ID_KEY = "session_id_key";
- private CallComposerContact contact;
+ private final Handler timeoutHandler = ThreadUtil.getUiThreadHandler();
+ private final Runnable sessionStartedTimedOut =
+ () -> {
+ LogUtil.i("CallComposerActivity.sessionStartedTimedOutRunnable", "session never started");
+ setFailedResultAndFinish();
+ };
+ private final Runnable placeTelecomCallRunnable =
+ () -> {
+ LogUtil.i("CallComposerActivity.placeTelecomCallRunnable", "upload timed out.");
+ placeTelecomCall();
+ };
+ // Counter for the number of message sent updates received from EnrichedCallManager
+ private int messageSentCounter;
+ private boolean pendingCallStarted;
+
+ private DialerContact contact;
private Long sessionId = Session.NO_SESSION_ID;
private TextView nameView;
@@ -113,23 +141,25 @@ public class CallComposerActivity extends AppCompatActivity
private View sendAndCall;
private TextView sendAndCallText;
+ private ProgressBar loading;
private ImageView cameraIcon;
private ImageView galleryIcon;
private ImageView messageIcon;
- private ViewPager pager;
+ private LockableViewPager pager;
private CallComposerPagerAdapter adapter;
private FrameLayout background;
private LinearLayout windowContainer;
+ private DialerExecutor<Uri> copyAndResizeExecutor;
private FastOutSlowInInterpolator interpolator;
private boolean shouldAnimateEntrance = true;
private boolean inFullscreenMode;
private boolean isSendAndCallHidingOrHidden = true;
- private boolean layoutChanged;
+ private boolean sendAndCallReady;
private int currentIndex;
- public static Intent newIntent(Context context, CallComposerContact contact) {
+ public static Intent newIntent(Context context, DialerContact contact) {
Intent intent = new Intent(context, CallComposerActivity.class);
ProtoParsers.put(intent, ARG_CALL_COMPOSER_CONTACT, contact);
return intent;
@@ -140,19 +170,20 @@ public class CallComposerActivity extends AppCompatActivity
super.onCreate(savedInstanceState);
setContentView(R.layout.call_composer_activity);
- nameView = (TextView) findViewById(R.id.contact_name);
- numberView = (TextView) findViewById(R.id.phone_number);
- contactPhoto = (QuickContactBadge) findViewById(R.id.contact_photo);
- cameraIcon = (ImageView) findViewById(R.id.call_composer_camera);
- galleryIcon = (ImageView) findViewById(R.id.call_composer_photo);
- messageIcon = (ImageView) findViewById(R.id.call_composer_message);
- contactContainer = (RelativeLayout) findViewById(R.id.contact_bar);
- pager = (ViewPager) findViewById(R.id.call_composer_view_pager);
- background = (FrameLayout) findViewById(R.id.background);
- windowContainer = (LinearLayout) findViewById(R.id.call_composer_container);
- toolbar = (DialerToolbar) findViewById(R.id.toolbar);
+ nameView = findViewById(R.id.contact_name);
+ numberView = findViewById(R.id.phone_number);
+ contactPhoto = findViewById(R.id.contact_photo);
+ cameraIcon = findViewById(R.id.call_composer_camera);
+ galleryIcon = findViewById(R.id.call_composer_photo);
+ messageIcon = findViewById(R.id.call_composer_message);
+ contactContainer = findViewById(R.id.contact_bar);
+ pager = findViewById(R.id.call_composer_view_pager);
+ background = findViewById(R.id.background);
+ windowContainer = findViewById(R.id.call_composer_container);
+ toolbar = findViewById(R.id.toolbar);
sendAndCall = findViewById(R.id.send_and_call_button);
- sendAndCallText = (TextView) findViewById(R.id.send_and_call_text);
+ sendAndCallText = findViewById(R.id.send_and_call_text);
+ loading = findViewById(R.id.call_composer_loading);
interpolator = new FastOutSlowInInterpolator();
adapter =
@@ -162,7 +193,6 @@ public class CallComposerActivity extends AppCompatActivity
pager.setAdapter(adapter);
pager.addOnPageChangeListener(this);
- background.addOnLayoutChangeListener(this);
cameraIcon.setOnClickListener(this);
galleryIcon.setOnClickListener(this);
messageIcon.setOnClickListener(this);
@@ -172,17 +202,13 @@ public class CallComposerActivity extends AppCompatActivity
if (savedInstanceState != null) {
shouldAnimateEntrance = savedInstanceState.getBoolean(ENTRANCE_ANIMATION_KEY);
+ sendAndCallReady = savedInstanceState.getBoolean(SEND_AND_CALL_READY_KEY);
pager.onRestoreInstanceState(savedInstanceState.getParcelable(VIEW_PAGER_STATE_KEY));
currentIndex = savedInstanceState.getInt(CURRENT_INDEX_KEY);
sessionId = savedInstanceState.getLong(SESSION_ID_KEY, Session.NO_SESSION_ID);
onPageSelected(currentIndex);
}
- int adjustMode =
- isLandscapeLayout()
- ? LayoutParams.SOFT_INPUT_ADJUST_PAN
- : LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
- getWindow().setSoftInputMode(adjustMode);
// Since we can't animate the views until they are ready to be drawn, we use this listener to
// track that and animate the call compose UI as soon as it's ready.
ViewUtil.doOnPreDraw(
@@ -194,13 +220,42 @@ public class CallComposerActivity extends AppCompatActivity
});
setMediaIconSelected(currentIndex);
+
+ copyAndResizeExecutor =
+ DialerExecutors.createUiTaskBuilder(
+ getFragmentManager(),
+ "copyAndResizeImageToSend",
+ new CopyAndResizeImageWorker(this.getApplicationContext()))
+ .onSuccess(this::onCopyAndResizeImageSuccess)
+ .onFailure(this::onCopyAndResizeImageFailure)
+ .build();
+ }
+
+ private void onCopyAndResizeImageSuccess(Pair<File, String> output) {
+ Uri shareableUri =
+ FileProvider.getUriForFile(
+ CallComposerActivity.this, Constants.get().getFileProviderAuthority(), output.first);
+
+ placeRCSCall(
+ MultimediaData.builder().setImage(grantUriPermission(shareableUri), output.second));
+ }
+
+ private void onCopyAndResizeImageFailure(Throwable throwable) {
+ // TODO(b/34279096) - gracefully handle message failure
+ LogUtil.e("CallComposerActivity.onCopyAndResizeImageFailure", "copy Failed", throwable);
}
@Override
protected void onResume() {
super.onResume();
getEnrichedCallManager().registerStateChangedListener(this);
- if (sessionId == Session.NO_SESSION_ID) {
+ if (pendingCallStarted) {
+ // User went into incall ui and pressed disconnect before the image was done uploading.
+ // Kill the activity and cancel the telecom call.
+ timeoutHandler.removeCallbacks(placeTelecomCallRunnable);
+ setResult(RESULT_OK);
+ finish();
+ } else if (sessionId == Session.NO_SESSION_ID) {
LogUtil.i("CallComposerActivity.onResume", "creating new session");
sessionId = getEnrichedCallManager().startCallComposerSession(contact.getNumber());
} else if (getEnrichedCallManager().getSession(sessionId) == null) {
@@ -218,11 +273,16 @@ public class CallComposerActivity extends AppCompatActivity
}
@Override
- protected void onPause() {
- super.onPause();
+ protected void onDestroy() {
+ super.onDestroy();
getEnrichedCallManager().unregisterStateChangedListener(this);
+ timeoutHandler.removeCallbacksAndMessages(null);
}
+ /**
+ * This listener is registered in onResume and removed in onDestroy, meaning that calls to this
+ * method can come after onStop and updates to UI could cause crashes.
+ */
@Override
public void onEnrichedCallStateChanged() {
refreshUiForCallComposerState();
@@ -240,12 +300,50 @@ public class CallComposerActivity extends AppCompatActivity
"state: %s",
StateExtension.toString(state));
- if (state == EnrichedCallManager.STATE_START_FAILED
- || state == EnrichedCallManager.STATE_CLOSED) {
- setFailedResultAndFinish();
+ switch (state) {
+ case Session.STATE_STARTING:
+ timeoutHandler.postDelayed(sessionStartedTimedOut, getSessionStartedTimeoutMillis());
+ if (sendAndCallReady) {
+ showLoadingUi();
+ }
+ break;
+ case Session.STATE_STARTED:
+ timeoutHandler.removeCallbacks(sessionStartedTimedOut);
+ if (sendAndCallReady) {
+ sendAndCall();
+ }
+ break;
+ case Session.STATE_START_FAILED:
+ case Session.STATE_CLOSED:
+ if (pendingCallStarted) {
+ placeTelecomCall();
+ } else {
+ setFailedResultAndFinish();
+ }
+ break;
+ case Session.STATE_MESSAGE_SENT:
+ if (++messageSentCounter == 3) {
+ // When we compose EC with images, there are 3 steps:
+ // 1. Message sent with no data
+ // 2. Image uploaded
+ // 3. url sent
+ // Once we receive 3 message sent updates, we know that we can proceed with the call.
+ timeoutHandler.removeCallbacks(placeTelecomCallRunnable);
+ placeTelecomCall();
+ }
+ break;
+ case Session.STATE_MESSAGE_FAILED:
+ case Session.STATE_NONE:
+ default:
+ break;
}
}
+ @VisibleForTesting
+ public long getSessionStartedTimeoutMillis() {
+ return ConfigProviderBindings.get(this).getLong("ec_session_started_timeout", 10_000);
+ }
+
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -264,13 +362,15 @@ public class CallComposerActivity extends AppCompatActivity
} else if (view == sendAndCall) {
sendAndCall();
} else {
- Assert.fail();
+ throw Assert.createIllegalStateFailException("View on click not implemented: " + view);
}
}
@Override
public void sendAndCall() {
if (!sessionReady()) {
+ sendAndCallReady = true;
+ showLoadingUi();
LogUtil.i("CallComposerActivity.onClick", "sendAndCall pressed, but the session isn't ready");
Logger.get(this)
.logImpression(
@@ -292,28 +392,8 @@ public class CallComposerActivity extends AppCompatActivity
GalleryComposerFragment galleryComposerFragment = (GalleryComposerFragment) fragment;
// If the current data is not a copy, make one.
if (!galleryComposerFragment.selectedDataIsCopy()) {
- DialerExecutors.createUiTaskBuilder(
- getFragmentManager(),
- "copyAndResizeImageToSend",
- new CopyAndResizeImageWorker(this.getApplicationContext()))
- .onSuccess(
- output -> {
- Uri shareableUri =
- FileProvider.getUriForFile(
- CallComposerActivity.this,
- Constants.get().getFileProviderAuthority(),
- output.first);
-
- builder.setImage(grantUriPermission(shareableUri), output.second);
- placeRCSCall(builder);
- })
- .onFailure(
- throwable -> {
- // TODO(b/34279096) - gracefully handle message failure
- LogUtil.e("CallComposerActivity.onCopyFailed", "copy Failed", throwable);
- })
- .build()
- .executeParallel(galleryComposerFragment.getGalleryData().getFileUri());
+ copyAndResizeExecutor.executeParallel(
+ galleryComposerFragment.getGalleryData().getFileUri());
} else {
Uri shareableUri =
FileProvider.getUriForFile(
@@ -338,19 +418,65 @@ public class CallComposerActivity extends AppCompatActivity
}
}
+ private void showLoadingUi() {
+ loading.setVisibility(View.VISIBLE);
+ pager.setSwipingLocked(true);
+ }
+
private boolean sessionReady() {
Session session = getEnrichedCallManager().getSession(sessionId);
- if (session == null) {
- return false;
+ return session != null && session.getState() == Session.STATE_STARTED;
+ }
+
+ @VisibleForTesting
+ public void placeRCSCall(MultimediaData.Builder builder) {
+ MultimediaData data = builder.build();
+ LogUtil.i("CallComposerActivity.placeRCSCall", "placing enriched call, data: " + data);
+ Logger.get(this).logImpression(DialerImpression.Type.CALL_COMPOSER_ACTIVITY_PLACE_RCS_CALL);
+
+ getEnrichedCallManager().sendCallComposerData(sessionId, data);
+ maybeShowPrivacyToast(data);
+ if (data.hasImageData()
+ && ConfigProviderBindings.get(this).getBoolean("enable_delayed_ec_images", true)
+ && !TelecomUtil.isInCall(this)) {
+ timeoutHandler.postDelayed(placeTelecomCallRunnable, getRCSTimeoutMillis());
+ startActivity(
+ CallPendingActivity.getIntent(
+ this,
+ contact.getNameOrNumber(),
+ contact.getDisplayNumber(),
+ contact.getNumberLabel(),
+ UriUtils.getLookupKeyFromUri(Uri.parse(contact.getContactUri())),
+ getString(R.string.call_composer_image_uploading),
+ Uri.parse(contact.getPhotoUri()),
+ sessionId));
+ pendingCallStarted = true;
+ } else {
+ placeTelecomCall();
}
+ }
- return session.getState() == EnrichedCallManager.STATE_STARTED;
+ private void maybeShowPrivacyToast(MultimediaData data) {
+ SharedPreferences preferences =
+ DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(this);
+ // Show a toast for privacy purposes if this is the first time a user uses call composer.
+ if (preferences.getBoolean(KEY_IS_FIRST_CALL_COMPOSE, true)) {
+ int privacyMessage =
+ data.hasImageData() ? R.string.image_sent_messages : R.string.message_sent_messages;
+ Toast toast = Toast.makeText(this, privacyMessage, Toast.LENGTH_LONG);
+ int yOffset = getResources().getDimensionPixelOffset(R.dimen.privacy_toast_y_offset);
+ toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, yOffset);
+ toast.show();
+ preferences.edit().putBoolean(KEY_IS_FIRST_CALL_COMPOSE, false).apply();
+ }
}
- private void placeRCSCall(MultimediaData.Builder builder) {
- LogUtil.i("CallComposerActivity.placeRCSCall", "placing enriched call");
- Logger.get(this).logImpression(DialerImpression.Type.CALL_COMPOSER_ACTIVITY_PLACE_RCS_CALL);
- getEnrichedCallManager().sendCallComposerData(sessionId, builder.build());
+ @VisibleForTesting
+ public long getRCSTimeoutMillis() {
+ return ConfigProviderBindings.get(this).getLong("ec_image_upload_timeout", 15_000);
+ }
+
+ private void placeTelecomCall() {
TelecomUtil.placeCall(
this,
new CallIntentBuilder(contact.getNumber(), CallInitiationType.Type.CALL_COMPOSER).build());
@@ -360,7 +486,7 @@ public class CallComposerActivity extends AppCompatActivity
/** Give permission to Messenger to view our image for RCS purposes. */
private Uri grantUriPermission(Uri uri) {
- // TODO: Move this to the enriched call manager.
+ // TODO(sail): Move this to the enriched call manager.
grantUriPermission(
"com.google.android.apps.messaging", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
return uri;
@@ -376,10 +502,6 @@ public class CallComposerActivity extends AppCompatActivity
}
if (currentIndex == CallComposerPagerAdapter.INDEX_MESSAGE) {
UiUtil.hideKeyboardFrom(this, windowContainer);
- } else if (position == CallComposerPagerAdapter.INDEX_MESSAGE
- && inFullscreenMode
- && !isLandscapeLayout()) {
- UiUtil.openKeyboardFrom(this, windowContainer);
}
currentIndex = position;
CallComposerFragment fragment = (CallComposerFragment) adapter.instantiateItem(pager, position);
@@ -398,6 +520,7 @@ public class CallComposerActivity extends AppCompatActivity
super.onSaveInstanceState(outState);
outState.putParcelable(VIEW_PAGER_STATE_KEY, pager.onSaveInstanceState());
outState.putBoolean(ENTRANCE_ANIMATION_KEY, shouldAnimateEntrance);
+ outState.putBoolean(SEND_AND_CALL_READY_KEY, sendAndCallReady);
outState.putInt(CURRENT_INDEX_KEY, currentIndex);
outState.putLong(SESSION_ID_KEY, sessionId);
}
@@ -424,28 +547,6 @@ public class CallComposerActivity extends AppCompatActivity
animateSendAndCall(fragment.shouldHide());
}
- // To detect when the keyboard changes.
- @Override
- public void onLayoutChange(
- View view,
- int left,
- int top,
- int right,
- int bottom,
- int oldLeft,
- int oldTop,
- int oldRight,
- int oldBottom) {
- // To prevent infinite layout change loops
- if (layoutChanged) {
- layoutChanged = false;
- return;
- }
-
- layoutChanged = true;
- showFullscreen(contactContainer.getTop() < 0 || inFullscreenMode);
- }
-
/**
* Reads arguments from the fragment arguments and populates the necessary instance variables.
* Copied from {@link com.android.contacts.common.dialog.CallSubjectDialog}.
@@ -456,14 +557,14 @@ public class CallComposerActivity extends AppCompatActivity
byte[] bytes =
Base64.decode(intent.getStringExtra(ARG_CALL_COMPOSER_CONTACT_BASE64), Base64.DEFAULT);
try {
- contact = CallComposerContact.parseFrom(bytes);
+ contact = DialerContact.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
throw Assert.createAssertionFailException(e.toString());
}
} else {
contact =
ProtoParsers.getTrusted(
- intent, ARG_CALL_COMPOSER_CONTACT, CallComposerContact.getDefaultInstance());
+ intent, ARG_CALL_COMPOSER_CONTACT, DialerContact.getDefaultInstance());
}
updateContactInfo();
}
@@ -634,12 +735,20 @@ public class CallComposerActivity extends AppCompatActivity
public void onAnimationStart(Animator animation) {
isSendAndCallHidingOrHidden = shouldHide;
sendAndCall.setVisibility(View.VISIBLE);
+ cameraIcon.setVisibility(View.VISIBLE);
+ galleryIcon.setVisibility(View.VISIBLE);
+ messageIcon.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
if (isSendAndCallHidingOrHidden) {
sendAndCall.setVisibility(View.INVISIBLE);
+ } else {
+ // hide buttons to prevent overdrawing and talkback discoverability
+ cameraIcon.setVisibility(View.GONE);
+ galleryIcon.setVisibility(View.GONE);
+ messageIcon.setVisibility(View.GONE);
}
}