1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dialer.callcomposer; 18 19 import android.animation.Animator; 20 import android.animation.Animator.AnimatorListener; 21 import android.animation.AnimatorSet; 22 import android.animation.ArgbEvaluator; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.Configuration; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.support.annotation.NonNull; 30 import android.support.v4.content.ContextCompat; 31 import android.support.v4.content.FileProvider; 32 import android.support.v4.view.ViewPager; 33 import android.support.v4.view.ViewPager.OnPageChangeListener; 34 import android.support.v4.view.animation.FastOutSlowInInterpolator; 35 import android.support.v7.app.AppCompatActivity; 36 import android.text.TextUtils; 37 import android.util.Base64; 38 import android.view.View; 39 import android.view.View.OnClickListener; 40 import android.view.View.OnLayoutChangeListener; 41 import android.view.ViewAnimationUtils; 42 import android.view.ViewGroup; 43 import android.view.WindowManager.LayoutParams; 44 import android.widget.FrameLayout; 45 import android.widget.ImageView; 46 import android.widget.LinearLayout; 47 import android.widget.QuickContactBadge; 48 import android.widget.RelativeLayout; 49 import android.widget.TextView; 50 import com.android.contacts.common.ContactPhotoManager; 51 import com.android.dialer.callcomposer.CallComposerFragment.CallComposerListener; 52 import com.android.dialer.callintent.CallInitiationType; 53 import com.android.dialer.callintent.CallIntentBuilder; 54 import com.android.dialer.common.Assert; 55 import com.android.dialer.common.LogUtil; 56 import com.android.dialer.common.UiUtil; 57 import com.android.dialer.common.concurrent.DialerExecutors; 58 import com.android.dialer.constants.Constants; 59 import com.android.dialer.enrichedcall.EnrichedCallComponent; 60 import com.android.dialer.enrichedcall.EnrichedCallManager; 61 import com.android.dialer.enrichedcall.EnrichedCallManager.State; 62 import com.android.dialer.enrichedcall.Session; 63 import com.android.dialer.enrichedcall.extensions.StateExtension; 64 import com.android.dialer.logging.DialerImpression; 65 import com.android.dialer.logging.Logger; 66 import com.android.dialer.multimedia.MultimediaData; 67 import com.android.dialer.protos.ProtoParsers; 68 import com.android.dialer.telecom.TelecomUtil; 69 import com.android.dialer.util.ViewUtil; 70 import com.android.dialer.widget.DialerToolbar; 71 import com.google.protobuf.InvalidProtocolBufferException; 72 import java.io.File; 73 74 /** 75 * Implements an activity which prompts for a call with additional media for an outgoing call. The 76 * activity includes a pop up with: 77 * 78 * <ul> 79 * <li>Contact galleryIcon 80 * <li>Name 81 * <li>Number 82 * <li>Media options to attach a gallery image, camera image or a message 83 * </ul> 84 */ 85 public class CallComposerActivity extends AppCompatActivity 86 implements OnClickListener, 87 OnPageChangeListener, 88 CallComposerListener, 89 OnLayoutChangeListener, 90 EnrichedCallManager.StateChangedListener { 91 92 public static final String KEY_CONTACT_NAME = "contact_name"; 93 94 private static final int ENTRANCE_ANIMATION_DURATION_MILLIS = 500; 95 private static final int EXIT_ANIMATION_DURATION_MILLIS = 500; 96 97 private static final String ARG_CALL_COMPOSER_CONTACT = "CALL_COMPOSER_CONTACT"; 98 private static final String ARG_CALL_COMPOSER_CONTACT_BASE64 = "CALL_COMPOSER_CONTACT_BASE64"; 99 100 private static final String ENTRANCE_ANIMATION_KEY = "entrance_animation_key"; 101 private static final String CURRENT_INDEX_KEY = "current_index_key"; 102 private static final String VIEW_PAGER_STATE_KEY = "view_pager_state_key"; 103 private static final String SESSION_ID_KEY = "session_id_key"; 104 105 private CallComposerContact contact; 106 private Long sessionId = Session.NO_SESSION_ID; 107 108 private TextView nameView; 109 private TextView numberView; 110 private QuickContactBadge contactPhoto; 111 private RelativeLayout contactContainer; 112 private DialerToolbar toolbar; 113 private View sendAndCall; 114 private TextView sendAndCallText; 115 116 private ImageView cameraIcon; 117 private ImageView galleryIcon; 118 private ImageView messageIcon; 119 private ViewPager pager; 120 private CallComposerPagerAdapter adapter; 121 122 private FrameLayout background; 123 private LinearLayout windowContainer; 124 125 private FastOutSlowInInterpolator interpolator; 126 private boolean shouldAnimateEntrance = true; 127 private boolean inFullscreenMode; 128 private boolean isSendAndCallHidingOrHidden = true; 129 private boolean layoutChanged; 130 private int currentIndex; 131 newIntent(Context context, CallComposerContact contact)132 public static Intent newIntent(Context context, CallComposerContact contact) { 133 Intent intent = new Intent(context, CallComposerActivity.class); 134 ProtoParsers.put(intent, ARG_CALL_COMPOSER_CONTACT, contact); 135 return intent; 136 } 137 138 @Override onCreate(Bundle savedInstanceState)139 protected void onCreate(Bundle savedInstanceState) { 140 super.onCreate(savedInstanceState); 141 setContentView(R.layout.call_composer_activity); 142 143 nameView = (TextView) findViewById(R.id.contact_name); 144 numberView = (TextView) findViewById(R.id.phone_number); 145 contactPhoto = (QuickContactBadge) findViewById(R.id.contact_photo); 146 cameraIcon = (ImageView) findViewById(R.id.call_composer_camera); 147 galleryIcon = (ImageView) findViewById(R.id.call_composer_photo); 148 messageIcon = (ImageView) findViewById(R.id.call_composer_message); 149 contactContainer = (RelativeLayout) findViewById(R.id.contact_bar); 150 pager = (ViewPager) findViewById(R.id.call_composer_view_pager); 151 background = (FrameLayout) findViewById(R.id.background); 152 windowContainer = (LinearLayout) findViewById(R.id.call_composer_container); 153 toolbar = (DialerToolbar) findViewById(R.id.toolbar); 154 sendAndCall = findViewById(R.id.send_and_call_button); 155 sendAndCallText = (TextView) findViewById(R.id.send_and_call_text); 156 157 interpolator = new FastOutSlowInInterpolator(); 158 adapter = 159 new CallComposerPagerAdapter( 160 getSupportFragmentManager(), 161 getResources().getInteger(R.integer.call_composer_message_limit)); 162 pager.setAdapter(adapter); 163 pager.addOnPageChangeListener(this); 164 165 background.addOnLayoutChangeListener(this); 166 cameraIcon.setOnClickListener(this); 167 galleryIcon.setOnClickListener(this); 168 messageIcon.setOnClickListener(this); 169 sendAndCall.setOnClickListener(this); 170 171 onHandleIntent(getIntent()); 172 173 if (savedInstanceState != null) { 174 shouldAnimateEntrance = savedInstanceState.getBoolean(ENTRANCE_ANIMATION_KEY); 175 pager.onRestoreInstanceState(savedInstanceState.getParcelable(VIEW_PAGER_STATE_KEY)); 176 currentIndex = savedInstanceState.getInt(CURRENT_INDEX_KEY); 177 sessionId = savedInstanceState.getLong(SESSION_ID_KEY, Session.NO_SESSION_ID); 178 onPageSelected(currentIndex); 179 } 180 181 int adjustMode = 182 isLandscapeLayout() 183 ? LayoutParams.SOFT_INPUT_ADJUST_PAN 184 : LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 185 getWindow().setSoftInputMode(adjustMode); 186 // Since we can't animate the views until they are ready to be drawn, we use this listener to 187 // track that and animate the call compose UI as soon as it's ready. 188 ViewUtil.doOnPreDraw( 189 windowContainer, 190 false, 191 () -> { 192 showFullscreen(inFullscreenMode); 193 runEntranceAnimation(); 194 }); 195 196 setMediaIconSelected(currentIndex); 197 } 198 199 @Override onResume()200 protected void onResume() { 201 super.onResume(); 202 getEnrichedCallManager().registerStateChangedListener(this); 203 if (sessionId == Session.NO_SESSION_ID) { 204 LogUtil.i("CallComposerActivity.onResume", "creating new session"); 205 sessionId = getEnrichedCallManager().startCallComposerSession(contact.getNumber()); 206 } else if (getEnrichedCallManager().getSession(sessionId) == null) { 207 LogUtil.i( 208 "CallComposerActivity.onResume", "session closed while activity paused, creating new"); 209 sessionId = getEnrichedCallManager().startCallComposerSession(contact.getNumber()); 210 } else { 211 LogUtil.i("CallComposerActivity.onResume", "session still open, using old"); 212 } 213 if (sessionId == Session.NO_SESSION_ID) { 214 LogUtil.w("CallComposerActivity.onResume", "failed to create call composer session"); 215 setFailedResultAndFinish(); 216 } 217 refreshUiForCallComposerState(); 218 } 219 220 @Override onPause()221 protected void onPause() { 222 super.onPause(); 223 getEnrichedCallManager().unregisterStateChangedListener(this); 224 } 225 226 @Override onEnrichedCallStateChanged()227 public void onEnrichedCallStateChanged() { 228 refreshUiForCallComposerState(); 229 } 230 refreshUiForCallComposerState()231 private void refreshUiForCallComposerState() { 232 Session session = getEnrichedCallManager().getSession(sessionId); 233 if (session == null) { 234 return; 235 } 236 237 @State int state = session.getState(); 238 LogUtil.i( 239 "CallComposerActivity.refreshUiForCallComposerState", 240 "state: %s", 241 StateExtension.toString(state)); 242 243 if (state == EnrichedCallManager.STATE_START_FAILED 244 || state == EnrichedCallManager.STATE_CLOSED) { 245 setFailedResultAndFinish(); 246 } 247 } 248 249 @Override onNewIntent(Intent intent)250 protected void onNewIntent(Intent intent) { 251 super.onNewIntent(intent); 252 onHandleIntent(intent); 253 } 254 255 @Override onClick(View view)256 public void onClick(View view) { 257 LogUtil.enterBlock("CallComposerActivity.onClick"); 258 if (view == cameraIcon) { 259 pager.setCurrentItem(CallComposerPagerAdapter.INDEX_CAMERA, true /* animate */); 260 } else if (view == galleryIcon) { 261 pager.setCurrentItem(CallComposerPagerAdapter.INDEX_GALLERY, true /* animate */); 262 } else if (view == messageIcon) { 263 pager.setCurrentItem(CallComposerPagerAdapter.INDEX_MESSAGE, true /* animate */); 264 } else if (view == sendAndCall) { 265 sendAndCall(); 266 } else { 267 Assert.fail(); 268 } 269 } 270 271 @Override sendAndCall()272 public void sendAndCall() { 273 if (!sessionReady()) { 274 LogUtil.i("CallComposerActivity.onClick", "sendAndCall pressed, but the session isn't ready"); 275 Logger.get(this) 276 .logImpression( 277 DialerImpression.Type 278 .CALL_COMPOSER_ACTIVITY_SEND_AND_CALL_PRESSED_WHEN_SESSION_NOT_READY); 279 return; 280 } 281 sendAndCall.setEnabled(false); 282 CallComposerFragment fragment = 283 (CallComposerFragment) adapter.instantiateItem(pager, currentIndex); 284 MultimediaData.Builder builder = MultimediaData.builder(); 285 286 if (fragment instanceof MessageComposerFragment) { 287 MessageComposerFragment messageComposerFragment = (MessageComposerFragment) fragment; 288 builder.setText(messageComposerFragment.getMessage()); 289 placeRCSCall(builder); 290 } 291 if (fragment instanceof GalleryComposerFragment) { 292 GalleryComposerFragment galleryComposerFragment = (GalleryComposerFragment) fragment; 293 // If the current data is not a copy, make one. 294 if (!galleryComposerFragment.selectedDataIsCopy()) { 295 DialerExecutors.createUiTaskBuilder( 296 getFragmentManager(), 297 "copyAndResizeImageToSend", 298 new CopyAndResizeImageWorker(this.getApplicationContext())) 299 .onSuccess( 300 output -> { 301 Uri shareableUri = 302 FileProvider.getUriForFile( 303 CallComposerActivity.this, 304 Constants.get().getFileProviderAuthority(), 305 output.first); 306 307 builder.setImage(grantUriPermission(shareableUri), output.second); 308 placeRCSCall(builder); 309 }) 310 .onFailure( 311 throwable -> { 312 // TODO(b/34279096) - gracefully handle message failure 313 LogUtil.e("CallComposerActivity.onCopyFailed", "copy Failed", throwable); 314 }) 315 .build() 316 .executeParallel(galleryComposerFragment.getGalleryData().getFileUri()); 317 } else { 318 Uri shareableUri = 319 FileProvider.getUriForFile( 320 this, 321 Constants.get().getFileProviderAuthority(), 322 new File(galleryComposerFragment.getGalleryData().getFilePath())); 323 324 builder.setImage( 325 grantUriPermission(shareableUri), 326 galleryComposerFragment.getGalleryData().getMimeType()); 327 328 placeRCSCall(builder); 329 } 330 } 331 if (fragment instanceof CameraComposerFragment) { 332 CameraComposerFragment cameraComposerFragment = (CameraComposerFragment) fragment; 333 cameraComposerFragment.getCameraUriWhenReady( 334 uri -> { 335 builder.setImage(grantUriPermission(uri), cameraComposerFragment.getMimeType()); 336 placeRCSCall(builder); 337 }); 338 } 339 } 340 sessionReady()341 private boolean sessionReady() { 342 Session session = getEnrichedCallManager().getSession(sessionId); 343 if (session == null) { 344 return false; 345 } 346 347 return session.getState() == EnrichedCallManager.STATE_STARTED; 348 } 349 placeRCSCall(MultimediaData.Builder builder)350 private void placeRCSCall(MultimediaData.Builder builder) { 351 LogUtil.i("CallComposerActivity.placeRCSCall", "placing enriched call"); 352 Logger.get(this).logImpression(DialerImpression.Type.CALL_COMPOSER_ACTIVITY_PLACE_RCS_CALL); 353 getEnrichedCallManager().sendCallComposerData(sessionId, builder.build()); 354 TelecomUtil.placeCall( 355 this, 356 new CallIntentBuilder(contact.getNumber(), CallInitiationType.Type.CALL_COMPOSER).build()); 357 setResult(RESULT_OK); 358 finish(); 359 } 360 361 /** Give permission to Messenger to view our image for RCS purposes. */ grantUriPermission(Uri uri)362 private Uri grantUriPermission(Uri uri) { 363 // TODO: Move this to the enriched call manager. 364 grantUriPermission( 365 "com.google.android.apps.messaging", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 366 return uri; 367 } 368 369 /** Animates {@code contactContainer} to align with content inside viewpager. */ 370 @Override onPageSelected(int position)371 public void onPageSelected(int position) { 372 if (position == CallComposerPagerAdapter.INDEX_MESSAGE) { 373 sendAndCallText.setText(R.string.send_and_call); 374 } else { 375 sendAndCallText.setText(R.string.share_and_call); 376 } 377 if (currentIndex == CallComposerPagerAdapter.INDEX_MESSAGE) { 378 UiUtil.hideKeyboardFrom(this, windowContainer); 379 } else if (position == CallComposerPagerAdapter.INDEX_MESSAGE 380 && inFullscreenMode 381 && !isLandscapeLayout()) { 382 UiUtil.openKeyboardFrom(this, windowContainer); 383 } 384 currentIndex = position; 385 CallComposerFragment fragment = (CallComposerFragment) adapter.instantiateItem(pager, position); 386 animateSendAndCall(fragment.shouldHide()); 387 setMediaIconSelected(position); 388 } 389 390 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)391 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} 392 393 @Override onPageScrollStateChanged(int state)394 public void onPageScrollStateChanged(int state) {} 395 396 @Override onSaveInstanceState(Bundle outState)397 protected void onSaveInstanceState(Bundle outState) { 398 super.onSaveInstanceState(outState); 399 outState.putParcelable(VIEW_PAGER_STATE_KEY, pager.onSaveInstanceState()); 400 outState.putBoolean(ENTRANCE_ANIMATION_KEY, shouldAnimateEntrance); 401 outState.putInt(CURRENT_INDEX_KEY, currentIndex); 402 outState.putLong(SESSION_ID_KEY, sessionId); 403 } 404 405 @Override onBackPressed()406 public void onBackPressed() { 407 if (!isSendAndCallHidingOrHidden) { 408 ((CallComposerFragment) adapter.instantiateItem(pager, currentIndex)).clearComposer(); 409 } else { 410 // Unregister first to avoid receiving a callback when the session closes 411 getEnrichedCallManager().unregisterStateChangedListener(this); 412 getEnrichedCallManager().endCallComposerSession(sessionId); 413 runExitAnimation(); 414 } 415 } 416 417 @Override composeCall(CallComposerFragment fragment)418 public void composeCall(CallComposerFragment fragment) { 419 // Since our ViewPager restores state to our fragments, it's possible that they could call 420 // #composeCall, so we have to check if the calling fragment is the current fragment. 421 if (adapter.instantiateItem(pager, currentIndex) != fragment) { 422 return; 423 } 424 animateSendAndCall(fragment.shouldHide()); 425 } 426 427 // To detect when the keyboard changes. 428 @Override onLayoutChange( View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)429 public void onLayoutChange( 430 View view, 431 int left, 432 int top, 433 int right, 434 int bottom, 435 int oldLeft, 436 int oldTop, 437 int oldRight, 438 int oldBottom) { 439 // To prevent infinite layout change loops 440 if (layoutChanged) { 441 layoutChanged = false; 442 return; 443 } 444 445 layoutChanged = true; 446 showFullscreen(contactContainer.getTop() < 0 || inFullscreenMode); 447 } 448 449 /** 450 * Reads arguments from the fragment arguments and populates the necessary instance variables. 451 * Copied from {@link com.android.contacts.common.dialog.CallSubjectDialog}. 452 */ 453 private void onHandleIntent(Intent intent) { 454 if (intent.getExtras().containsKey(ARG_CALL_COMPOSER_CONTACT_BASE64)) { 455 // Invoked from launch_call_composer.py. The proto is provided as a base64 encoded string. 456 byte[] bytes = 457 Base64.decode(intent.getStringExtra(ARG_CALL_COMPOSER_CONTACT_BASE64), Base64.DEFAULT); 458 try { 459 contact = CallComposerContact.parseFrom(bytes); 460 } catch (InvalidProtocolBufferException e) { 461 throw Assert.createAssertionFailException(e.toString()); 462 } 463 } else { 464 contact = 465 ProtoParsers.getTrusted( 466 intent, ARG_CALL_COMPOSER_CONTACT, CallComposerContact.getDefaultInstance()); 467 } 468 updateContactInfo(); 469 } 470 471 @Override 472 public boolean isLandscapeLayout() { 473 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; 474 } 475 476 /** Populates the contact info fields based on the current contact information. */ 477 private void updateContactInfo() { 478 ContactPhotoManager.getInstance(this) 479 .loadDialerThumbnailOrPhoto( 480 contactPhoto, 481 contact.hasContactUri() ? Uri.parse(contact.getContactUri()) : null, 482 contact.getPhotoId(), 483 contact.hasPhotoUri() ? Uri.parse(contact.getPhotoUri()) : null, 484 contact.getNameOrNumber(), 485 contact.getContactType()); 486 487 nameView.setText(contact.getNameOrNumber()); 488 toolbar.setTitle(contact.getNameOrNumber()); 489 if (!TextUtils.isEmpty(contact.getDisplayNumber())) { 490 numberView.setVisibility(View.VISIBLE); 491 String secondaryInfo = 492 TextUtils.isEmpty(contact.getNumberLabel()) 493 ? contact.getDisplayNumber() 494 : getString( 495 com.android.contacts.common.R.string.call_subject_type_and_number, 496 contact.getNumberLabel(), 497 contact.getDisplayNumber()); 498 numberView.setText(secondaryInfo); 499 toolbar.setSubtitle(secondaryInfo); 500 } else { 501 numberView.setVisibility(View.GONE); 502 numberView.setText(null); 503 } 504 } 505 506 /** Animates compose UI into view */ 507 private void runEntranceAnimation() { 508 if (!shouldAnimateEntrance) { 509 return; 510 } 511 shouldAnimateEntrance = false; 512 513 int value = isLandscapeLayout() ? windowContainer.getWidth() : windowContainer.getHeight(); 514 ValueAnimator contentAnimation = ValueAnimator.ofFloat(value, 0); 515 contentAnimation.setInterpolator(interpolator); 516 contentAnimation.setDuration(ENTRANCE_ANIMATION_DURATION_MILLIS); 517 contentAnimation.addUpdateListener( 518 animation -> { 519 if (isLandscapeLayout()) { 520 windowContainer.setX((Float) animation.getAnimatedValue()); 521 } else { 522 windowContainer.setY((Float) animation.getAnimatedValue()); 523 } 524 }); 525 526 if (!isLandscapeLayout()) { 527 int colorFrom = ContextCompat.getColor(this, android.R.color.transparent); 528 int colorTo = ContextCompat.getColor(this, R.color.call_composer_background_color); 529 ValueAnimator backgroundAnimation = 530 ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); 531 backgroundAnimation.setInterpolator(interpolator); 532 backgroundAnimation.setDuration(ENTRANCE_ANIMATION_DURATION_MILLIS); // milliseconds 533 backgroundAnimation.addUpdateListener( 534 animator -> background.setBackgroundColor((int) animator.getAnimatedValue())); 535 536 AnimatorSet set = new AnimatorSet(); 537 set.play(contentAnimation).with(backgroundAnimation); 538 set.start(); 539 } else { 540 contentAnimation.start(); 541 } 542 } 543 544 /** Animates compose UI out of view and ends the activity. */ runExitAnimation()545 private void runExitAnimation() { 546 int value = isLandscapeLayout() ? windowContainer.getWidth() : windowContainer.getHeight(); 547 ValueAnimator contentAnimation = ValueAnimator.ofFloat(0, value); 548 contentAnimation.setInterpolator(interpolator); 549 contentAnimation.setDuration(EXIT_ANIMATION_DURATION_MILLIS); 550 contentAnimation.addUpdateListener( 551 animation -> { 552 if (isLandscapeLayout()) { 553 windowContainer.setX((Float) animation.getAnimatedValue()); 554 } else { 555 windowContainer.setY((Float) animation.getAnimatedValue()); 556 } 557 if (animation.getAnimatedFraction() > .95) { 558 finish(); 559 } 560 }); 561 562 if (!isLandscapeLayout()) { 563 int colorTo = ContextCompat.getColor(this, android.R.color.transparent); 564 int colorFrom = ContextCompat.getColor(this, R.color.call_composer_background_color); 565 ValueAnimator backgroundAnimation = 566 ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); 567 backgroundAnimation.setInterpolator(interpolator); 568 backgroundAnimation.setDuration(EXIT_ANIMATION_DURATION_MILLIS); 569 backgroundAnimation.addUpdateListener( 570 animator -> background.setBackgroundColor((int) animator.getAnimatedValue())); 571 572 AnimatorSet set = new AnimatorSet(); 573 set.play(contentAnimation).with(backgroundAnimation); 574 set.start(); 575 } else { 576 contentAnimation.start(); 577 } 578 } 579 580 @Override showFullscreen(boolean fullscreen)581 public void showFullscreen(boolean fullscreen) { 582 inFullscreenMode = fullscreen; 583 ViewGroup.LayoutParams layoutParams = pager.getLayoutParams(); 584 if (isLandscapeLayout()) { 585 layoutParams.height = background.getHeight(); 586 toolbar.setVisibility(View.INVISIBLE); 587 contactContainer.setVisibility(View.GONE); 588 } else if (fullscreen || getResources().getBoolean(R.bool.show_toolbar)) { 589 layoutParams.height = background.getHeight() - toolbar.getHeight(); 590 toolbar.setVisibility(View.VISIBLE); 591 contactContainer.setVisibility(View.GONE); 592 } else { 593 layoutParams.height = 594 getResources().getDimensionPixelSize(R.dimen.call_composer_view_pager_height); 595 toolbar.setVisibility(View.INVISIBLE); 596 contactContainer.setVisibility(View.VISIBLE); 597 } 598 pager.setLayoutParams(layoutParams); 599 } 600 601 @Override isFullscreen()602 public boolean isFullscreen() { 603 return inFullscreenMode; 604 } 605 animateSendAndCall(final boolean shouldHide)606 private void animateSendAndCall(final boolean shouldHide) { 607 // createCircularReveal doesn't respect animations being disabled, handle it here. 608 if (ViewUtil.areAnimationsDisabled(this)) { 609 isSendAndCallHidingOrHidden = shouldHide; 610 sendAndCall.setVisibility(shouldHide ? View.INVISIBLE : View.VISIBLE); 611 return; 612 } 613 614 // If the animation is changing directions, start it again. Else do nothing. 615 if (isSendAndCallHidingOrHidden != shouldHide) { 616 int centerX = sendAndCall.getWidth() / 2; 617 int centerY = sendAndCall.getHeight() / 2; 618 int startRadius = shouldHide ? centerX : 0; 619 int endRadius = shouldHide ? 0 : centerX; 620 621 // When the device rotates and state is restored, the send and call button may not be attached 622 // yet and this causes a crash when we attempt to to reveal it. To prevent this, we wait until 623 // {@code sendAndCall} is ready, then animate and reveal it. 624 ViewUtil.doOnPreDraw( 625 sendAndCall, 626 true, 627 () -> { 628 Animator animator = 629 ViewAnimationUtils.createCircularReveal( 630 sendAndCall, centerX, centerY, startRadius, endRadius); 631 animator.addListener( 632 new AnimatorListener() { 633 @Override 634 public void onAnimationStart(Animator animation) { 635 isSendAndCallHidingOrHidden = shouldHide; 636 sendAndCall.setVisibility(View.VISIBLE); 637 } 638 639 @Override 640 public void onAnimationEnd(Animator animation) { 641 if (isSendAndCallHidingOrHidden) { 642 sendAndCall.setVisibility(View.INVISIBLE); 643 } 644 } 645 646 @Override 647 public void onAnimationCancel(Animator animation) {} 648 649 @Override 650 public void onAnimationRepeat(Animator animation) {} 651 }); 652 animator.start(); 653 }); 654 } 655 } 656 setMediaIconSelected(int position)657 private void setMediaIconSelected(int position) { 658 float alpha = 0.7f; 659 cameraIcon.setAlpha(position == CallComposerPagerAdapter.INDEX_CAMERA ? 1 : alpha); 660 galleryIcon.setAlpha(position == CallComposerPagerAdapter.INDEX_GALLERY ? 1 : alpha); 661 messageIcon.setAlpha(position == CallComposerPagerAdapter.INDEX_MESSAGE ? 1 : alpha); 662 } 663 setFailedResultAndFinish()664 private void setFailedResultAndFinish() { 665 setResult( 666 RESULT_FIRST_USER, new Intent().putExtra(KEY_CONTACT_NAME, contact.getNameOrNumber())); 667 finish(); 668 } 669 670 @NonNull getEnrichedCallManager()671 private EnrichedCallManager getEnrichedCallManager() { 672 return EnrichedCallComponent.get(this).getEnrichedCallManager(); 673 } 674 } 675