diff options
Diffstat (limited to 'java/com/android/incallui/video/impl')
51 files changed, 2322 insertions, 0 deletions
diff --git a/java/com/android/incallui/video/impl/AndroidManifest.xml b/java/com/android/incallui/video/impl/AndroidManifest.xml new file mode 100644 index 000000000..a36828e29 --- /dev/null +++ b/java/com/android/incallui/video/impl/AndroidManifest.xml @@ -0,0 +1,3 @@ +<manifest + package="com.android.incallui.video.impl"> +</manifest> diff --git a/java/com/android/incallui/video/impl/CameraPermissionDialogFragment.java b/java/com/android/incallui/video/impl/CameraPermissionDialogFragment.java new file mode 100644 index 000000000..291fce4a0 --- /dev/null +++ b/java/com/android/incallui/video/impl/CameraPermissionDialogFragment.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui.video.impl; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import com.android.dialer.common.FragmentUtils; + +/** Dialog fragment to ask for camera permission from user. */ +public class CameraPermissionDialogFragment extends DialogFragment { + + static CameraPermissionDialogFragment newInstance() { + CameraPermissionDialogFragment fragment = new CameraPermissionDialogFragment(); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle bundle) { + return new AlertDialog.Builder(getContext()) + .setTitle(R.string.camera_permission_dialog_title) + .setMessage(R.string.camera_permission_dialog_message) + .setPositiveButton( + R.string.camera_permission_dialog_positive_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + VideoCallFragment fragment = + FragmentUtils.getParentUnsafe( + CameraPermissionDialogFragment.this, VideoCallFragment.class); + fragment.onCameraPermissionGranted(); + } + }) + .setNegativeButton( + R.string.camera_permission_dialog_negative_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .create(); + } +} diff --git a/java/com/android/incallui/video/impl/CheckableImageButton.java b/java/com/android/incallui/video/impl/CheckableImageButton.java new file mode 100644 index 000000000..320f0571a --- /dev/null +++ b/java/com/android/incallui/video/impl/CheckableImageButton.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui.video.impl; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.SoundEffectConstants; +import android.widget.Checkable; +import android.widget.ImageButton; + +/** Image button that maintains a checked state. */ +public class CheckableImageButton extends ImageButton implements Checkable { + + private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; + + /** Callback interface to notify when the button's checked state has changed */ + public interface OnCheckedChangeListener { + + void onCheckedChanged(CheckableImageButton button, boolean isChecked); + } + + private boolean broadcasting; + private boolean isChecked; + private OnCheckedChangeListener onCheckedChangeListener; + private CharSequence contentDescriptionChecked; + private CharSequence contentDescriptionUnchecked; + + public CheckableImageButton(Context context) { + this(context, null); + } + + public CheckableImageButton(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CheckableImageButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CheckableImageButton); + setChecked(typedArray.getBoolean(R.styleable.CheckableImageButton_android_checked, false)); + contentDescriptionChecked = + typedArray.getText(R.styleable.CheckableImageButton_contentDescriptionChecked); + contentDescriptionUnchecked = + typedArray.getText(R.styleable.CheckableImageButton_contentDescriptionUnchecked); + typedArray.recycle(); + + updateContentDescription(); + setClickable(true); + setFocusable(true); + } + + @Override + public void setChecked(boolean checked) { + performSetChecked(checked); + } + + /** + * Called when the state of the button should be updated, this should not be the result of user + * interaction. + * + * @param checked {@code true} if the button should be in the checked state, {@code false} + * otherwise. + */ + private void performSetChecked(boolean checked) { + if (isChecked() == checked) { + return; + } + isChecked = checked; + CharSequence contentDescription = updateContentDescription(); + announceForAccessibility(contentDescription); + refreshDrawableState(); + } + + private CharSequence updateContentDescription() { + CharSequence contentDescription = + isChecked ? contentDescriptionChecked : contentDescriptionUnchecked; + setContentDescription(contentDescription); + return contentDescription; + } + + /** + * Called when the user interacts with a button. This should not result in the button updating + * state, rather the request should be propagated to the associated listener. + * + * @param checked {@code true} if the button should be in the checked state, {@code false} + * otherwise. + */ + private void userRequestedSetChecked(boolean checked) { + if (isChecked() == checked) { + return; + } + if (broadcasting) { + return; + } + broadcasting = true; + if (onCheckedChangeListener != null) { + onCheckedChangeListener.onCheckedChanged(this, checked); + } + broadcasting = false; + } + + @Override + public boolean isChecked() { + return isChecked; + } + + @Override + public void toggle() { + userRequestedSetChecked(!isChecked()); + } + + @Override + public int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + if (isChecked()) { + mergeDrawableStates(drawableState, CHECKED_STATE_SET); + } + return drawableState; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + invalidate(); + } + + public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { + this.onCheckedChangeListener = listener; + } + + @Override + public boolean performClick() { + if (!isCheckable()) { + return super.performClick(); + } + + toggle(); + final boolean handled = super.performClick(); + if (!handled) { + // View only makes a sound effect if the onClickListener was + // called, so we'll need to make one here instead. + playSoundEffect(SoundEffectConstants.CLICK); + } + return handled; + } + + private boolean isCheckable() { + return onCheckedChangeListener != null; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState) state; + super.onRestoreInstanceState(savedState.getSuperState()); + performSetChecked(savedState.isChecked); + requestLayout(); + } + + @Override + protected Parcelable onSaveInstanceState() { + return new SavedState(isChecked(), super.onSaveInstanceState()); + } + + private static class SavedState extends BaseSavedState { + + public final boolean isChecked; + + private SavedState(boolean isChecked, Parcelable superState) { + super(superState); + this.isChecked = isChecked; + } + + protected SavedState(Parcel in) { + super(in); + isChecked = in.readByte() != 0; + } + + public static final Creator<SavedState> CREATOR = + new Creator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (isChecked ? 1 : 0)); + } + } +} diff --git a/java/com/android/incallui/video/impl/SpeakerButtonController.java b/java/com/android/incallui/video/impl/SpeakerButtonController.java new file mode 100644 index 000000000..e12032abf --- /dev/null +++ b/java/com/android/incallui/video/impl/SpeakerButtonController.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui.video.impl; + +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.telecom.CallAudioState; +import android.view.View; +import android.view.View.OnClickListener; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.incallui.incall.protocol.InCallButtonUiDelegate; +import com.android.incallui.video.impl.CheckableImageButton.OnCheckedChangeListener; +import com.android.incallui.video.protocol.VideoCallScreenDelegate; + +/** Manages a single button. */ +public class SpeakerButtonController implements OnCheckedChangeListener, OnClickListener { + + @NonNull private final InCallButtonUiDelegate inCallButtonUiDelegate; + @NonNull private final VideoCallScreenDelegate videoCallScreenDelegate; + + @NonNull private CheckableImageButton button; + + @DrawableRes private int icon = R.drawable.quantum_ic_volume_up_white_36; + + private boolean isChecked; + private boolean checkable; + private boolean isEnabled; + private CharSequence contentDescription; + + public SpeakerButtonController( + @NonNull CheckableImageButton button, + @NonNull InCallButtonUiDelegate inCallButtonUiDelegate, + @NonNull VideoCallScreenDelegate videoCallScreenDelegate) { + this.inCallButtonUiDelegate = Assert.isNotNull(inCallButtonUiDelegate); + this.videoCallScreenDelegate = Assert.isNotNull(videoCallScreenDelegate); + this.button = Assert.isNotNull(button); + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public void updateButtonState() { + button.setVisibility(View.VISIBLE); + button.setEnabled(isEnabled); + button.setChecked(isChecked); + button.setOnClickListener(checkable ? null : this); + button.setOnCheckedChangeListener(checkable ? this : null); + button.setImageResource(icon); + button.setContentDescription(contentDescription); + } + + public void setAudioState(CallAudioState audioState) { + LogUtil.i("SpeakerButtonController.setSupportedAudio", "audioState: " + audioState); + + @StringRes int contentDescriptionResId; + if ((audioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) + == CallAudioState.ROUTE_BLUETOOTH) { + checkable = false; + isChecked = false; + + if ((audioState.getRoute() & CallAudioState.ROUTE_BLUETOOTH) + == CallAudioState.ROUTE_BLUETOOTH) { + icon = R.drawable.quantum_ic_bluetooth_audio_white_36; + contentDescriptionResId = R.string.incall_content_description_bluetooth; + } else if ((audioState.getRoute() & CallAudioState.ROUTE_SPEAKER) + == CallAudioState.ROUTE_SPEAKER) { + icon = R.drawable.quantum_ic_volume_up_white_36; + contentDescriptionResId = R.string.incall_content_description_speaker; + } else if ((audioState.getRoute() & CallAudioState.ROUTE_WIRED_HEADSET) + == CallAudioState.ROUTE_WIRED_HEADSET) { + icon = R.drawable.quantum_ic_headset_white_36; + contentDescriptionResId = R.string.incall_content_description_headset; + } else { + icon = R.drawable.ic_phone_audio_white_36dp; + contentDescriptionResId = R.string.incall_content_description_earpiece; + } + } else { + checkable = true; + isChecked = audioState.getRoute() == CallAudioState.ROUTE_SPEAKER; + icon = R.drawable.quantum_ic_volume_up_white_36; + contentDescriptionResId = R.string.incall_content_description_speaker; + } + + contentDescription = button.getContext().getText(contentDescriptionResId); + updateButtonState(); + } + + @Override + public void onCheckedChanged(CheckableImageButton button, boolean isChecked) { + LogUtil.i("SpeakerButtonController.onCheckedChanged", null); + inCallButtonUiDelegate.toggleSpeakerphone(); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } + + @Override + public void onClick(View view) { + LogUtil.i("SpeakerButtonController.onClick", null); + inCallButtonUiDelegate.showAudioRouteSelector(); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } +} diff --git a/java/com/android/incallui/video/impl/SwitchOnHoldCallController.java b/java/com/android/incallui/video/impl/SwitchOnHoldCallController.java new file mode 100644 index 000000000..372b56b4e --- /dev/null +++ b/java/com/android/incallui/video/impl/SwitchOnHoldCallController.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui.video.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.View.OnClickListener; +import com.android.dialer.common.Assert; +import com.android.incallui.incall.protocol.InCallScreenDelegate; +import com.android.incallui.incall.protocol.SecondaryInfo; +import com.android.incallui.video.protocol.VideoCallScreenDelegate; + +/** Manages the swap button and on hold banner. */ +public class SwitchOnHoldCallController implements OnClickListener { + + @NonNull private InCallScreenDelegate inCallScreenDelegate; + @NonNull private VideoCallScreenDelegate videoCallScreenDelegate; + + @NonNull private View switchOnHoldButton; + + @NonNull private View onHoldBanner; + + private boolean isVisible; + + private boolean isEnabled; + + @Nullable private SecondaryInfo secondaryInfo; + + public SwitchOnHoldCallController( + @NonNull View switchOnHoldButton, + @NonNull View onHoldBanner, + @NonNull InCallScreenDelegate inCallScreenDelegate, + @NonNull VideoCallScreenDelegate videoCallScreenDelegate) { + this.switchOnHoldButton = Assert.isNotNull(switchOnHoldButton); + switchOnHoldButton.setOnClickListener(this); + this.onHoldBanner = Assert.isNotNull(onHoldBanner); + this.inCallScreenDelegate = Assert.isNotNull(inCallScreenDelegate); + this.videoCallScreenDelegate = Assert.isNotNull(videoCallScreenDelegate); + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + updateButtonState(); + } + + public void setVisible(boolean isVisible) { + this.isVisible = isVisible; + updateButtonState(); + } + + public void setOnScreen() { + isVisible = hasSecondaryInfo(); + updateButtonState(); + } + + public void setSecondaryInfo(@Nullable SecondaryInfo secondaryInfo) { + this.secondaryInfo = secondaryInfo; + isVisible = hasSecondaryInfo(); + } + + private boolean hasSecondaryInfo() { + return secondaryInfo != null && secondaryInfo.shouldShow; + } + + public void updateButtonState() { + switchOnHoldButton.setEnabled(isEnabled); + switchOnHoldButton.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE); + onHoldBanner.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public void onClick(View view) { + inCallScreenDelegate.onSecondaryInfoClicked(); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } +} diff --git a/java/com/android/incallui/video/impl/VideoCallFragment.java b/java/com/android/incallui/video/impl/VideoCallFragment.java new file mode 100644 index 000000000..77a67d032 --- /dev/null +++ b/java/com/android/incallui/video/impl/VideoCallFragment.java @@ -0,0 +1,1215 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui.video.impl; + +import android.Manifest.permission; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Outline; +import android.graphics.Point; +import android.graphics.drawable.Animatable; +import android.os.Bundle; +import android.os.SystemClock; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.animation.FastOutLinearInInterpolator; +import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.telecom.CallAudioState; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnSystemUiVisibilityChangeListener; +import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewOutlineProvider; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import com.android.dialer.common.Assert; +import com.android.dialer.common.FragmentUtils; +import com.android.dialer.common.LogUtil; +import com.android.dialer.compat.ActivityCompat; +import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment; +import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter; +import com.android.incallui.call.VideoUtils; +import com.android.incallui.contactgrid.ContactGridManager; +import com.android.incallui.hold.OnHoldFragment; +import com.android.incallui.incall.protocol.InCallButtonIds; +import com.android.incallui.incall.protocol.InCallButtonIdsExtension; +import com.android.incallui.incall.protocol.InCallButtonUi; +import com.android.incallui.incall.protocol.InCallButtonUiDelegate; +import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; +import com.android.incallui.incall.protocol.InCallScreen; +import com.android.incallui.incall.protocol.InCallScreenDelegate; +import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; +import com.android.incallui.incall.protocol.PrimaryCallState; +import com.android.incallui.incall.protocol.PrimaryInfo; +import com.android.incallui.incall.protocol.SecondaryInfo; +import com.android.incallui.video.impl.CheckableImageButton.OnCheckedChangeListener; +import com.android.incallui.video.protocol.VideoCallScreen; +import com.android.incallui.video.protocol.VideoCallScreenDelegate; +import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory; +import com.android.incallui.videosurface.bindings.VideoSurfaceBindings; +import com.android.incallui.videosurface.protocol.VideoSurfaceTexture; + +/** Contains UI elements for a video call. */ +public class VideoCallFragment extends Fragment + implements InCallScreen, + InCallButtonUi, + VideoCallScreen, + OnClickListener, + OnCheckedChangeListener, + AudioRouteSelectorPresenter, + OnSystemUiVisibilityChangeListener { + + private static final float BLUR_PREVIEW_RADIUS = 16.0f; + private static final float BLUR_PREVIEW_SCALE_FACTOR = 1.0f; + private static final float BLUR_REMOTE_RADIUS = 25.0f; + private static final float BLUR_REMOTE_SCALE_FACTOR = 0.25f; + private static final float ASPECT_RATIO_MATCH_THRESHOLD = 0.2f; + + private static final int CAMERA_PERMISSION_REQUEST_CODE = 1; + private static final String CAMERA_PERMISSION_DIALOG_FRAMENT_TAG = + "CameraPermissionDialogFragment"; + private static final long CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS = 2000L; + private static final long VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS = 2000L; + + private final ViewOutlineProvider circleOutlineProvider = + new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + int x = view.getWidth() / 2; + int y = view.getHeight() / 2; + int radius = Math.min(x, y); + outline.setOval(x - radius, y - radius, x + radius, y + radius); + } + }; + private InCallScreenDelegate inCallScreenDelegate; + private VideoCallScreenDelegate videoCallScreenDelegate; + private InCallButtonUiDelegate inCallButtonUiDelegate; + private View endCallButton; + private CheckableImageButton speakerButton; + private SpeakerButtonController speakerButtonController; + private CheckableImageButton muteButton; + private CheckableImageButton cameraOffButton; + private ImageButton swapCameraButton; + private View switchOnHoldButton; + private View onHoldContainer; + private SwitchOnHoldCallController switchOnHoldCallController; + private TextView remoteVideoOff; + private ImageView remoteOffBlurredImageView; + private View mutePreviewOverlay; + private View previewOffOverlay; + private ImageView previewOffBlurredImageView; + private View controls; + private View controlsContainer; + private TextureView previewTextureView; + private TextureView remoteTextureView; + private View greenScreenBackgroundView; + private View fullscreenBackgroundView; + private boolean shouldShowRemote; + private boolean shouldShowPreview; + private boolean isInFullscreenMode; + private boolean isInGreenScreenMode; + private boolean hasInitializedScreenModes; + private boolean isRemotelyHeld; + private ContactGridManager contactGridManager; + private SecondaryInfo savedSecondaryInfo; + private final Runnable cameraPermissionDialogRunnable = + new Runnable() { + @Override + public void run() { + if (videoCallScreenDelegate.shouldShowCameraPermissionDialog()) { + LogUtil.i("VideoCallFragment.cameraPermissionDialogRunnable", "showing dialog"); + checkCameraPermission(); + } + } + }; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LogUtil.i("VideoCallFragment.onCreate", null); + + inCallButtonUiDelegate = + FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class) + .newInCallButtonUiDelegate(); + if (savedInstanceState != null) { + inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState); + } + } + + @Override + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission granted."); + videoCallScreenDelegate.onCameraPermissionGranted(); + } else { + LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission denied."); + } + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Nullable + @Override + public View onCreateView( + LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) { + LogUtil.i("VideoCallFragment.onCreateView", null); + + View view = + layoutInflater.inflate( + isLandscape() ? R.layout.frag_videocall_land : R.layout.frag_videocall, + viewGroup, + false); + contactGridManager = + new ContactGridManager(view, null /* no avatar */, 0, false /* showAnonymousAvatar */); + + controls = view.findViewById(R.id.videocall_video_controls); + controls.setVisibility( + ActivityCompat.isInMultiWindowMode(getActivity()) ? View.GONE : View.VISIBLE); + controlsContainer = view.findViewById(R.id.videocall_video_controls_container); + speakerButton = (CheckableImageButton) view.findViewById(R.id.videocall_speaker_button); + muteButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_button); + muteButton.setOnCheckedChangeListener(this); + mutePreviewOverlay = view.findViewById(R.id.videocall_video_preview_mute_overlay); + cameraOffButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_video); + cameraOffButton.setOnCheckedChangeListener(this); + previewOffOverlay = view.findViewById(R.id.videocall_video_preview_off_overlay); + previewOffBlurredImageView = + (ImageView) view.findViewById(R.id.videocall_preview_off_blurred_image_view); + swapCameraButton = (ImageButton) view.findViewById(R.id.videocall_switch_video); + swapCameraButton.setOnClickListener(this); + view.findViewById(R.id.videocall_switch_controls) + .setVisibility( + ActivityCompat.isInMultiWindowMode(getActivity()) ? View.GONE : View.VISIBLE); + switchOnHoldButton = view.findViewById(R.id.videocall_switch_on_hold); + onHoldContainer = view.findViewById(R.id.videocall_on_hold_banner); + remoteVideoOff = (TextView) view.findViewById(R.id.videocall_remote_video_off); + remoteVideoOff.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); + remoteOffBlurredImageView = + (ImageView) view.findViewById(R.id.videocall_remote_off_blurred_image_view); + endCallButton = view.findViewById(R.id.videocall_end_call); + endCallButton.setOnClickListener(this); + previewTextureView = (TextureView) view.findViewById(R.id.videocall_video_preview); + previewTextureView.setClipToOutline(true); + previewOffOverlay.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + checkCameraPermission(); + } + }); + remoteTextureView = (TextureView) view.findViewById(R.id.videocall_video_remote); + greenScreenBackgroundView = view.findViewById(R.id.videocall_green_screen_background); + fullscreenBackgroundView = view.findViewById(R.id.videocall_fullscreen_background); + + // We need the texture view size to be able to scale the remote video. At this point the view + // layout won't be complete so add a layout listener. + ViewTreeObserver observer = remoteTextureView.getViewTreeObserver(); + observer.addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + LogUtil.i("VideoCallFragment.onGlobalLayout", null); + updateRemoteVideoScaling(); + updatePreviewVideoScaling(); + updateVideoOffViews(); + // Remove the listener so we don't continually re-layout. + ViewTreeObserver observer = remoteTextureView.getViewTreeObserver(); + if (observer.isAlive()) { + observer.removeOnGlobalLayoutListener(this); + } + } + }); + + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle bundle) { + super.onViewCreated(view, bundle); + LogUtil.i("VideoCallFragment.onViewCreated", null); + + inCallScreenDelegate = + FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class) + .newInCallScreenDelegate(); + videoCallScreenDelegate = + FragmentUtils.getParentUnsafe(this, VideoCallScreenDelegateFactory.class) + .newVideoCallScreenDelegate(); + + speakerButtonController = + new SpeakerButtonController(speakerButton, inCallButtonUiDelegate, videoCallScreenDelegate); + switchOnHoldCallController = + new SwitchOnHoldCallController( + switchOnHoldButton, onHoldContainer, inCallScreenDelegate, videoCallScreenDelegate); + + videoCallScreenDelegate.initVideoCallScreenDelegate(getContext(), this); + + inCallScreenDelegate.onInCallScreenDelegateInit(this); + inCallScreenDelegate.onInCallScreenReady(); + inCallButtonUiDelegate.onInCallButtonUiReady(this); + + view.setOnSystemUiVisibilityChangeListener(this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + inCallButtonUiDelegate.onSaveInstanceState(outState); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + LogUtil.i("VideoCallFragment.onDestroyView", null); + inCallButtonUiDelegate.onInCallButtonUiUnready(); + inCallScreenDelegate.onInCallScreenUnready(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (savedSecondaryInfo != null) { + setSecondary(savedSecondaryInfo); + } + } + + @Override + public void onResume() { + super.onResume(); + LogUtil.i("VideoCallFragment.onResume", null); + inCallScreenDelegate.onInCallScreenResumed(); + } + + @Override + public void onStart() { + super.onStart(); + LogUtil.i("VideoCallFragment.onStart", null); + inCallButtonUiDelegate.refreshMuteState(); + videoCallScreenDelegate.onVideoCallScreenUiReady(); + getView().postDelayed(cameraPermissionDialogRunnable, CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS); + } + + @Override + public void onPause() { + super.onPause(); + LogUtil.i("VideoCallFragment.onPause", null); + } + + @Override + public void onStop() { + super.onStop(); + LogUtil.i("VideoCallFragment.onStop", null); + getView().removeCallbacks(cameraPermissionDialogRunnable); + videoCallScreenDelegate.onVideoCallScreenUiUnready(); + } + + private void exitFullscreenMode() { + LogUtil.i("VideoCallFragment.exitFullscreenMode", null); + + if (!getView().isAttachedToWindow()) { + LogUtil.i("VideoCallFragment.exitFullscreenMode", "not attached"); + return; + } + + showSystemUI(); + + LinearOutSlowInInterpolator linearOutSlowInInterpolator = new LinearOutSlowInInterpolator(); + + // Animate the controls to the shown state. + controls + .animate() + .translationX(0) + .translationY(0) + .setInterpolator(linearOutSlowInInterpolator) + .alpha(1) + .start(); + + // Animate onHold to the shown state. + switchOnHoldButton + .animate() + .translationX(0) + .translationY(0) + .setInterpolator(linearOutSlowInInterpolator) + .alpha(1) + .withStartAction( + new Runnable() { + @Override + public void run() { + switchOnHoldCallController.setOnScreen(); + } + }); + + View contactGridView = contactGridManager.getContainerView(); + // Animate contact grid to the shown state. + contactGridView + .animate() + .translationX(0) + .translationY(0) + .setInterpolator(linearOutSlowInInterpolator) + .alpha(1) + .withStartAction( + new Runnable() { + @Override + public void run() { + contactGridManager.show(); + } + }); + + endCallButton + .animate() + .translationX(0) + .translationY(0) + .setInterpolator(linearOutSlowInInterpolator) + .alpha(1) + .withStartAction( + new Runnable() { + @Override + public void run() { + endCallButton.setVisibility(View.VISIBLE); + } + }) + .start(); + + // Animate all the preview controls up to make room for the navigation bar. + // In green screen mode we don't need this because the preview takes up the whole screen and has + // a fixed position. + if (!isInGreenScreenMode) { + Point previewOffsetStartShown = getPreviewOffsetStartShown(); + for (View view : getAllPreviewRelatedViews()) { + // Animate up with the preview offset above the navigation bar. + view.animate() + .translationX(previewOffsetStartShown.x) + .translationY(previewOffsetStartShown.y) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .start(); + } + } + + updateOverlayBackground(); + } + + private void showSystemUI() { + View view = getView(); + if (view != null) { + // Code is more expressive with all flags present, even though some may be combined + //noinspection PointlessBitwiseExpression + view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + } + + /** Set view flags to hide the system UI. System UI will return on any touch event */ + private void hideSystemUI() { + View view = getView(); + if (view != null) { + view.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + } + + private Point getControlsOffsetEndHidden(View controls) { + if (isLandscape()) { + return new Point(0, getOffsetBottom(controls)); + } else { + return new Point(getOffsetStart(controls), 0); + } + } + + private Point getSwitchOnHoldOffsetEndHidden(View swapCallButton) { + if (isLandscape()) { + return new Point(0, getOffsetTop(swapCallButton)); + } else { + return new Point(getOffsetEnd(swapCallButton), 0); + } + } + + private Point getContactGridOffsetEndHidden(View view) { + return new Point(0, getOffsetTop(view)); + } + + private Point getEndCallOffsetEndHidden(View endCallButton) { + if (isLandscape()) { + return new Point(getOffsetEnd(endCallButton), 0); + } else { + return new Point(0, ((MarginLayoutParams) endCallButton.getLayoutParams()).bottomMargin); + } + } + + private Point getPreviewOffsetStartShown() { + // No insets in multiwindow mode, and rootWindowInsets will get the display's insets. + if (ActivityCompat.isInMultiWindowMode(getActivity())) { + return new Point(); + } + if (isLandscape()) { + int stableInsetEnd = + getView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? getView().getRootWindowInsets().getStableInsetLeft() + : -getView().getRootWindowInsets().getStableInsetRight(); + return new Point(stableInsetEnd, 0); + } else { + return new Point(0, -getView().getRootWindowInsets().getStableInsetBottom()); + } + } + + private View[] getAllPreviewRelatedViews() { + return new View[] { + previewTextureView, previewOffOverlay, previewOffBlurredImageView, mutePreviewOverlay, + }; + } + + private int getOffsetTop(View view) { + return -(view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).topMargin); + } + + private int getOffsetBottom(View view) { + return view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).bottomMargin; + } + + private int getOffsetStart(View view) { + int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginStart(); + if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + offset = -offset; + } + return -offset; + } + + private int getOffsetEnd(View view) { + int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginEnd(); + if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + offset = -offset; + } + return offset; + } + + private void enterFullscreenMode() { + LogUtil.i("VideoCallFragment.enterFullscreenMode", null); + + hideSystemUI(); + + Interpolator fastOutLinearInInterpolator = new FastOutLinearInInterpolator(); + + // Animate controls to the hidden state. + Point offset = getControlsOffsetEndHidden(controls); + controls + .animate() + .translationX(offset.x) + .translationY(offset.y) + .setInterpolator(fastOutLinearInInterpolator) + .alpha(0) + .start(); + + // Animate onHold to the hidden state. + offset = getSwitchOnHoldOffsetEndHidden(switchOnHoldButton); + switchOnHoldButton + .animate() + .translationX(offset.x) + .translationY(offset.y) + .setInterpolator(fastOutLinearInInterpolator) + .alpha(0); + + View contactGridView = contactGridManager.getContainerView(); + // Animate contact grid to the hidden state. + offset = getContactGridOffsetEndHidden(contactGridView); + contactGridView + .animate() + .translationX(offset.x) + .translationY(offset.y) + .setInterpolator(fastOutLinearInInterpolator) + .alpha(0); + + offset = getEndCallOffsetEndHidden(endCallButton); + // Use a fast out interpolator to quickly fade out the button. This is important because the + // button can't draw under the navigation bar which means that it'll look weird if it just + // abruptly disappears when it reaches the edge of the naivgation bar. + endCallButton + .animate() + .translationX(offset.x) + .translationY(offset.y) + .setInterpolator(fastOutLinearInInterpolator) + .alpha(0) + .withEndAction( + new Runnable() { + @Override + public void run() { + endCallButton.setVisibility(View.INVISIBLE); + } + }) + .setInterpolator(new FastOutLinearInInterpolator()) + .start(); + + // Animate all the preview controls down now that the navigation bar is hidden. + // In green screen mode we don't need this because the preview takes up the whole screen and has + // a fixed position. + if (!isInGreenScreenMode) { + for (View view : getAllPreviewRelatedViews()) { + // Animate down with the navigation bar hidden. + view.animate() + .translationX(0) + .translationY(0) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .start(); + } + } + updateOverlayBackground(); + } + + @Override + public void onClick(View v) { + if (v == endCallButton) { + LogUtil.i("VideoCallFragment.onClick", "end call button clicked"); + inCallButtonUiDelegate.onEndCallClicked(); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } else if (v == swapCameraButton) { + if (swapCameraButton.getDrawable() instanceof Animatable) { + ((Animatable) swapCameraButton.getDrawable()).start(); + } + inCallButtonUiDelegate.toggleCameraClicked(); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } + } + + @Override + public void onCheckedChanged(CheckableImageButton button, boolean isChecked) { + if (button == cameraOffButton) { + if (!isChecked && !VideoUtils.hasCameraPermissionAndAllowedByUser(getContext())) { + LogUtil.i("VideoCallFragment.onCheckedChanged", "show camera permission dialog"); + checkCameraPermission(); + } else { + inCallButtonUiDelegate.pauseVideoClicked(isChecked); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } + } else if (button == muteButton) { + inCallButtonUiDelegate.muteClicked(isChecked); + videoCallScreenDelegate.resetAutoFullscreenTimer(); + } + } + + @Override + public void showVideoViews( + boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld) { + LogUtil.i( + "VideoCallFragment.showVideoViews", + "showPreview: %b, shouldShowRemote: %b", + shouldShowPreview, + shouldShowRemote); + this.shouldShowPreview = shouldShowPreview; + this.shouldShowRemote = shouldShowRemote; + this.isRemotelyHeld = isRemotelyHeld; + + videoCallScreenDelegate.getLocalVideoSurfaceTexture().attachToTextureView(previewTextureView); + videoCallScreenDelegate.getRemoteVideoSurfaceTexture().attachToTextureView(remoteTextureView); + + updateVideoOffViews(); + updateRemoteVideoScaling(); + } + + /** + * This method scales the video feed inside the texture view, it doesn't change the texture view's + * size. In the old UI we would change the view size to match the aspect ratio of the video. In + * the new UI the view is always square (with the circular clip) so we have to do additional work + * to make sure the non-square video doesn't look squished. + */ + @Override + public void onLocalVideoDimensionsChanged() { + LogUtil.i("VideoCallFragment.onLocalVideoDimensionsChanged", null); + updatePreviewVideoScaling(); + } + + @Override + public void onLocalVideoOrientationChanged() { + LogUtil.i("VideoCallFragment.onLocalVideoOrientationChanged", null); + updatePreviewVideoScaling(); + } + + /** Called when the remote video's dimensions change. */ + @Override + public void onRemoteVideoDimensionsChanged() { + LogUtil.i("VideoCallFragment.onRemoteVideoDimensionsChanged", null); + updateRemoteVideoScaling(); + } + + @Override + public void updateFullscreenAndGreenScreenMode( + boolean shouldShowFullscreen, boolean shouldShowGreenScreen) { + LogUtil.i( + "VideoCallFragment.updateFullscreenAndGreenScreenMode", + "shouldShowFullscreen: %b, shouldShowGreenScreen: %b", + shouldShowFullscreen, + shouldShowGreenScreen); + + if (getActivity() == null) { + LogUtil.i("VideoCallFragment.updateFullscreenAndGreenScreenMode", "not attached to activity"); + return; + } + + // Check if anything is actually going to change. The first time this function is called we + // force a change by checking the hasInitializedScreenModes flag. We also force both fullscreen + // and green screen modes to update even if only one has changed. That's because they both + // depend on each other. + if (hasInitializedScreenModes + && shouldShowGreenScreen == isInGreenScreenMode + && shouldShowFullscreen == isInFullscreenMode) { + LogUtil.i( + "VideoCallFragment.updateFullscreenAndGreenScreenMode", "no change to screen modes"); + return; + } + hasInitializedScreenModes = true; + isInGreenScreenMode = shouldShowGreenScreen; + isInFullscreenMode = shouldShowFullscreen; + + if (getView().isAttachedToWindow() && !ActivityCompat.isInMultiWindowMode(getActivity())) { + controlsContainer.onApplyWindowInsets(getView().getRootWindowInsets()); + } + if (shouldShowGreenScreen) { + enterGreenScreenMode(); + } else { + exitGreenScreenMode(); + } + if (shouldShowFullscreen) { + enterFullscreenMode(); + } else { + exitFullscreenMode(); + } + updateVideoOffViews(); + + OnHoldFragment onHoldFragment = + ((OnHoldFragment) + getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner)); + if (onHoldFragment != null) { + onHoldFragment.setPadTopInset(!isInFullscreenMode); + } + } + + @Override + public Fragment getVideoCallScreenFragment() { + return this; + } + + @Override + public void showButton(@InCallButtonIds int buttonId, boolean show) { + LogUtil.v( + "VideoCallFragment.showButton", + "buttonId: %s, show: %b", + InCallButtonIdsExtension.toString(buttonId), + show); + if (buttonId == InCallButtonIds.BUTTON_AUDIO) { + speakerButtonController.setEnabled(show); + } else if (buttonId == InCallButtonIds.BUTTON_MUTE) { + muteButton.setEnabled(show); + } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) { + cameraOffButton.setEnabled(show); + } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { + switchOnHoldCallController.setVisible(show); + } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_CAMERA) { + swapCameraButton.setEnabled(show); + } + } + + @Override + public void enableButton(@InCallButtonIds int buttonId, boolean enable) { + LogUtil.v( + "VideoCallFragment.setEnabled", + "buttonId: %s, enable: %b", + InCallButtonIdsExtension.toString(buttonId), + enable); + if (buttonId == InCallButtonIds.BUTTON_AUDIO) { + speakerButtonController.setEnabled(enable); + } else if (buttonId == InCallButtonIds.BUTTON_MUTE) { + muteButton.setEnabled(enable); + } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) { + cameraOffButton.setEnabled(enable); + } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { + switchOnHoldCallController.setEnabled(enable); + } + } + + @Override + public void setEnabled(boolean enabled) { + LogUtil.v("VideoCallFragment.setEnabled", "enabled: " + enabled); + speakerButtonController.setEnabled(enabled); + muteButton.setEnabled(enabled); + cameraOffButton.setEnabled(enabled); + switchOnHoldCallController.setEnabled(enabled); + } + + @Override + public void setHold(boolean value) { + LogUtil.i("VideoCallFragment.setHold", "value: " + value); + } + + @Override + public void setCameraSwitched(boolean isBackFacingCamera) { + LogUtil.i("VideoCallFragment.setCameraSwitched", "isBackFacingCamera: " + isBackFacingCamera); + } + + @Override + public void setVideoPaused(boolean isPaused) { + LogUtil.i("VideoCallFragment.setVideoPaused", "isPaused: " + isPaused); + cameraOffButton.setChecked(isPaused); + } + + @Override + public void setAudioState(CallAudioState audioState) { + LogUtil.i("VideoCallFragment.setAudioState", "audioState: " + audioState); + speakerButtonController.setAudioState(audioState); + muteButton.setChecked(audioState.isMuted()); + updateMutePreviewOverlayVisibility(); + } + + @Override + public void updateButtonStates() { + LogUtil.i("VideoCallFragment.updateButtonState", null); + speakerButtonController.updateButtonState(); + switchOnHoldCallController.updateButtonState(); + } + + @Override + public void updateInCallButtonUiColors() {} + + @Override + public Fragment getInCallButtonUiFragment() { + return this; + } + + @Override + public void showAudioRouteSelector() { + LogUtil.i("VideoCallFragment.showAudioRouteSelector", null); + AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState()) + .show(getChildFragmentManager(), null); + } + + @Override + public void onAudioRouteSelected(int audioRoute) { + LogUtil.i("VideoCallFragment.onAudioRouteSelected", "audioRoute: " + audioRoute); + inCallButtonUiDelegate.setAudioRoute(audioRoute); + } + + @Override + public void setPrimary(@NonNull PrimaryInfo primaryInfo) { + LogUtil.i("VideoCallFragment.setPrimary", primaryInfo.toString()); + contactGridManager.setPrimary(primaryInfo); + } + + @Override + public void setSecondary(@NonNull SecondaryInfo secondaryInfo) { + LogUtil.i("VideoCallFragment.setSecondary", secondaryInfo.toString()); + if (!isAdded()) { + savedSecondaryInfo = secondaryInfo; + return; + } + savedSecondaryInfo = null; + switchOnHoldCallController.setSecondaryInfo(secondaryInfo); + updateButtonStates(); + FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); + Fragment oldBanner = getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner); + if (secondaryInfo.shouldShow) { + OnHoldFragment onHoldFragment = OnHoldFragment.newInstance(secondaryInfo); + onHoldFragment.setPadTopInset(!isInFullscreenMode); + transaction.replace(R.id.videocall_on_hold_banner, onHoldFragment); + } else { + if (oldBanner != null) { + transaction.remove(oldBanner); + } + } + transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top); + transaction.commitAllowingStateLoss(); + } + + @Override + public void setCallState(@NonNull PrimaryCallState primaryCallState) { + LogUtil.i("VideoCallFragment.setCallState", primaryCallState.toString()); + contactGridManager.setCallState(primaryCallState); + } + + @Override + public void setEndCallButtonEnabled(boolean enabled, boolean animate) { + LogUtil.i("VideoCallFragment.setEndCallButtonEnabled", "enabled: " + enabled); + } + + @Override + public void showManageConferenceCallButton(boolean visible) { + LogUtil.i("VideoCallFragment.showManageConferenceCallButton", "visible: " + visible); + } + + @Override + public boolean isManageConferenceVisible() { + LogUtil.i("VideoCallFragment.isManageConferenceVisible", null); + return false; + } + + @Override + public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + contactGridManager.dispatchPopulateAccessibilityEvent(event); + } + + @Override + public void showNoteSentToast() { + LogUtil.i("VideoCallFragment.showNoteSentToast", null); + } + + @Override + public void updateInCallScreenColors() { + LogUtil.i("VideoCallFragment.updateColors", null); + } + + @Override + public void onInCallScreenDialpadVisibilityChange(boolean isShowing) { + LogUtil.i("VideoCallFragment.onInCallScreenDialpadVisibilityChange", null); + } + + @Override + public int getAnswerAndDialpadContainerResourceId() { + return 0; + } + + @Override + public Fragment getInCallScreenFragment() { + return this; + } + + @Override + public boolean isShowingLocationUi() { + return false; + } + + @Override + public void showLocationUi(Fragment locationUi) { + LogUtil.e("VideoCallFragment.showLocationUi", "Emergency video calling not supported"); + // Do nothing + } + + private void updatePreviewVideoScaling() { + if (previewTextureView.getWidth() == 0 || previewTextureView.getHeight() == 0) { + LogUtil.i("VideoCallFragment.updatePreviewVideoScaling", "view layout hasn't finished yet"); + return; + } + VideoSurfaceTexture localVideoSurfaceTexture = + videoCallScreenDelegate.getLocalVideoSurfaceTexture(); + Point cameraDimensions = localVideoSurfaceTexture.getSurfaceDimensions(); + if (cameraDimensions == null) { + LogUtil.i( + "VideoCallFragment.updatePreviewVideoScaling", "camera dimensions haven't been set"); + return; + } + if (isLandscape()) { + VideoSurfaceBindings.scaleVideoAndFillView( + previewTextureView, + cameraDimensions.x, + cameraDimensions.y, + videoCallScreenDelegate.getDeviceOrientation()); + } else { + VideoSurfaceBindings.scaleVideoAndFillView( + previewTextureView, + cameraDimensions.y, + cameraDimensions.x, + videoCallScreenDelegate.getDeviceOrientation()); + } + } + + private void updateRemoteVideoScaling() { + VideoSurfaceTexture remoteVideoSurfaceTexture = + videoCallScreenDelegate.getRemoteVideoSurfaceTexture(); + Point videoSize = remoteVideoSurfaceTexture.getSourceVideoDimensions(); + if (videoSize == null) { + LogUtil.i("VideoCallFragment.updateRemoteVideoScaling", "video size is null"); + return; + } + if (remoteTextureView.getWidth() == 0 || remoteTextureView.getHeight() == 0) { + LogUtil.i("VideoCallFragment.updateRemoteVideoScaling", "view layout hasn't finished yet"); + return; + } + + // If the video and display aspect ratio's are close then scale video to fill display + float videoAspectRatio = ((float) videoSize.x) / videoSize.y; + float displayAspectRatio = + ((float) remoteTextureView.getWidth()) / remoteTextureView.getHeight(); + float delta = Math.abs(videoAspectRatio - displayAspectRatio); + float sum = videoAspectRatio + displayAspectRatio; + if (delta / sum < ASPECT_RATIO_MATCH_THRESHOLD) { + VideoSurfaceBindings.scaleVideoAndFillView(remoteTextureView, videoSize.x, videoSize.y, 0); + } else { + VideoSurfaceBindings.scaleVideoMaintainingAspectRatio( + remoteTextureView, videoSize.x, videoSize.y); + } + } + + private boolean isLandscape() { + // Choose orientation based on display orientation, not window orientation + int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation(); + return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270; + } + + private void enterGreenScreenMode() { + LogUtil.i("VideoCallFragment.enterGreenScreenMode", null); + RelativeLayout.LayoutParams params = + new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); + params.addRule(RelativeLayout.ALIGN_PARENT_START); + params.addRule(RelativeLayout.ALIGN_PARENT_TOP); + previewTextureView.setLayoutParams(params); + previewTextureView.setOutlineProvider(null); + updatePreviewVideoScaling(); + updateOverlayBackground(); + contactGridManager.setIsMiddleRowVisible(true); + updateMutePreviewOverlayVisibility(); + + previewOffBlurredImageView.setLayoutParams(params); + previewOffBlurredImageView.setOutlineProvider(null); + previewOffBlurredImageView.setClipToOutline(false); + } + + private void exitGreenScreenMode() { + LogUtil.i("VideoCallFragment.exitGreenScreenMode", null); + Resources resources = getResources(); + RelativeLayout.LayoutParams params = + new RelativeLayout.LayoutParams( + (int) resources.getDimension(R.dimen.videocall_preview_width), + (int) resources.getDimension(R.dimen.videocall_preview_height)); + params.setMargins( + 0, 0, 0, (int) resources.getDimension(R.dimen.videocall_preview_margin_bottom)); + if (isLandscape()) { + params.addRule(RelativeLayout.ALIGN_PARENT_END); + params.setMarginEnd((int) resources.getDimension(R.dimen.videocall_preview_margin_end)); + } else { + params.addRule(RelativeLayout.ALIGN_PARENT_START); + params.setMarginStart((int) resources.getDimension(R.dimen.videocall_preview_margin_start)); + } + params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + previewTextureView.setLayoutParams(params); + previewTextureView.setOutlineProvider(circleOutlineProvider); + updatePreviewVideoScaling(); + updateOverlayBackground(); + contactGridManager.setIsMiddleRowVisible(false); + updateMutePreviewOverlayVisibility(); + + previewOffBlurredImageView.setLayoutParams(params); + previewOffBlurredImageView.setOutlineProvider(circleOutlineProvider); + previewOffBlurredImageView.setClipToOutline(true); + } + + private void updateVideoOffViews() { + // Always hide the preview off and remote off views in green screen mode. + boolean previewEnabled = isInGreenScreenMode || shouldShowPreview; + previewOffOverlay.setVisibility(previewEnabled ? View.GONE : View.VISIBLE); + updateBlurredImageView( + previewTextureView, + previewOffBlurredImageView, + shouldShowPreview, + BLUR_PREVIEW_RADIUS, + BLUR_PREVIEW_SCALE_FACTOR); + + boolean remoteEnabled = isInGreenScreenMode || shouldShowRemote; + boolean isResumed = remoteEnabled && !isRemotelyHeld; + if (isResumed) { + boolean wasRemoteVideoOff = + TextUtils.equals( + remoteVideoOff.getText(), + remoteVideoOff.getResources().getString(R.string.videocall_remote_video_off)); + // The text needs to be updated and hidden after enough delay in order to be announced by + // talkback. + remoteVideoOff.setText( + wasRemoteVideoOff + ? R.string.videocall_remote_video_on + : R.string.videocall_remotely_resumed); + remoteVideoOff.postDelayed( + new Runnable() { + @Override + public void run() { + remoteVideoOff.setVisibility(View.GONE); + } + }, + VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS); + } else { + remoteVideoOff.setText( + isRemotelyHeld ? R.string.videocall_remotely_held : R.string.videocall_remote_video_off); + remoteVideoOff.setVisibility(View.VISIBLE); + } + LogUtil.i("VideoCallFragment.updateVideoOffViews", "calling updateBlurredImageView"); + updateBlurredImageView( + remoteTextureView, + remoteOffBlurredImageView, + shouldShowRemote, + BLUR_REMOTE_RADIUS, + BLUR_REMOTE_SCALE_FACTOR); + } + + private void updateBlurredImageView( + TextureView textureView, + ImageView blurredImageView, + boolean isVideoEnabled, + float blurRadius, + float scaleFactor) { + boolean didBlur = false; + long startTimeMillis = SystemClock.elapsedRealtime(); + if (!isVideoEnabled) { + int width = Math.round(textureView.getWidth() * scaleFactor); + int height = Math.round(textureView.getHeight() * scaleFactor); + // This call takes less than 10 milliseconds. + Bitmap bitmap = textureView.getBitmap(width, height); + if (bitmap != null) { + // TODO: When the view is first displayed after a rotation the bitmap is empty + // and thus this blur has no effect. + // This call can take 100 milliseconds. + blur(getContext(), bitmap, blurRadius); + + // TODO: Figure out why only have to apply the transform in landscape mode + if (width > height) { + bitmap = + Bitmap.createBitmap( + bitmap, + 0, + 0, + bitmap.getWidth(), + bitmap.getHeight(), + textureView.getTransform(null), + true); + } + + blurredImageView.setImageBitmap(bitmap); + blurredImageView.setVisibility(View.VISIBLE); + didBlur = true; + } + } + if (!didBlur) { + blurredImageView.setImageBitmap(null); + blurredImageView.setVisibility(View.GONE); + } + + LogUtil.i( + "VideoCallFragment.updateBlurredImageView", + "didBlur: %b, took %d millis", + didBlur, + (SystemClock.elapsedRealtime() - startTimeMillis)); + } + + private void updateOverlayBackground() { + if (isInGreenScreenMode) { + // We want to darken the preview view to make text and buttons readable. The fullscreen + // background is below the preview view so use the green screen background instead. + animateSetVisibility(greenScreenBackgroundView, View.VISIBLE); + animateSetVisibility(fullscreenBackgroundView, View.GONE); + } else if (!isInFullscreenMode) { + // We want to darken the remote view to make text and buttons readable. The green screen + // background is above the preview view so it would darken the preview too. Use the fullscreen + // background instead. + animateSetVisibility(greenScreenBackgroundView, View.GONE); + animateSetVisibility(fullscreenBackgroundView, View.VISIBLE); + } else { + animateSetVisibility(greenScreenBackgroundView, View.GONE); + animateSetVisibility(fullscreenBackgroundView, View.GONE); + } + } + + private void updateMutePreviewOverlayVisibility() { + // Normally the mute overlay shows on the bottom right of the preview bubble. In green screen + // mode the preview is fullscreen so there's no where to anchor it. + mutePreviewOverlay.setVisibility( + muteButton.isChecked() && !isInGreenScreenMode ? View.VISIBLE : View.GONE); + } + + private static void animateSetVisibility(final View view, final int visibility) { + if (view.getVisibility() == visibility) { + return; + } + + int startAlpha; + int endAlpha; + if (visibility == View.GONE) { + startAlpha = 1; + endAlpha = 0; + } else if (visibility == View.VISIBLE) { + startAlpha = 0; + endAlpha = 1; + } else { + Assert.fail(); + return; + } + + view.setAlpha(startAlpha); + view.setVisibility(View.VISIBLE); + view.animate() + .alpha(endAlpha) + .withEndAction( + new Runnable() { + @Override + public void run() { + view.setVisibility(visibility); + } + }) + .start(); + } + + private static void blur(Context context, Bitmap image, float blurRadius) { + RenderScript renderScript = RenderScript.create(context); + ScriptIntrinsicBlur blurScript = + ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)); + Allocation allocationIn = Allocation.createFromBitmap(renderScript, image); + Allocation allocationOut = Allocation.createFromBitmap(renderScript, image); + blurScript.setRadius(blurRadius); + blurScript.setInput(allocationIn); + blurScript.forEach(allocationOut); + allocationOut.copyTo(image); + } + + @Override + public void onSystemUiVisibilityChange(int visibility) { + boolean navBarVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + videoCallScreenDelegate.onSystemUiVisibilityChange(navBarVisible); + } + + protected void onCameraPermissionGranted() { + videoCallScreenDelegate.onCameraPermissionGranted(); + } + + private void checkCameraPermission() { + // Checks if user has consent of camera permission and the permission is granted. + // If camera permission is revoked, shows system permission dialog. + // If camera permission is granted but user doesn't have consent of camera permission + // (which means it's first time making video call), shows custom dialog instead. This + // will only be shown to user once. + if (!VideoUtils.hasCameraPermissionAndAllowedByUser(getContext())) { + videoCallScreenDelegate.onCameraPermissionDialogShown(); + if (!VideoUtils.hasCameraPermission(getContext())) { + requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); + } else { + CameraPermissionDialogFragment.newInstance() + .show(getChildFragmentManager(), CAMERA_PERMISSION_DIALOG_FRAMENT_TAG); + } + } + } +} diff --git a/java/com/android/incallui/video/impl/res/color/videocall_button_icon_tint.xml b/java/com/android/incallui/video/impl/res/color/videocall_button_icon_tint.xml new file mode 100644 index 000000000..b46607b1b --- /dev/null +++ b/java/com/android/incallui/video/impl/res/color/videocall_button_icon_tint.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="#ff000000" android:state_checked="true"/> + <item android:color="#ffffffff"/> +</selector> diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/ic_switch_camera.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/ic_switch_camera.png Binary files differnew file mode 100644 index 000000000..b5c6f0a87 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/ic_switch_camera.png diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked.png Binary files differnew file mode 100644 index 000000000..2ab2f21a7 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked.png diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked_disabled.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked_disabled.png Binary files differnew file mode 100644 index 000000000..2deaadd76 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked_pressed.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked_pressed.png Binary files differnew file mode 100644 index 000000000..c4147fa62 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_checked_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_default.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_default.png Binary files differnew file mode 100644 index 000000000..c59e21504 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_default.png diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_disabled.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_disabled.png Binary files differnew file mode 100644 index 000000000..95d6824f5 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_pressed.png b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_pressed.png Binary files differnew file mode 100644 index 000000000..9a525a374 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-hdpi/video_button_bg_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/ic_switch_camera.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/ic_switch_camera.png Binary files differnew file mode 100644 index 000000000..f3427a02e --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/ic_switch_camera.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked.png Binary files differnew file mode 100644 index 000000000..c3ff7b2bb --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked_disabled.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked_disabled.png Binary files differnew file mode 100644 index 000000000..c75281332 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked_pressed.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked_pressed.png Binary files differnew file mode 100644 index 000000000..fd16baef7 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_checked_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_default.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_default.png Binary files differnew file mode 100644 index 000000000..3fe2446e3 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_default.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_disabled.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_disabled.png Binary files differnew file mode 100644 index 000000000..1ff3e7c25 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_pressed.png b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_pressed.png Binary files differnew file mode 100644 index 000000000..aa7289af1 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-mdpi/video_button_bg_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/ic_switch_camera.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/ic_switch_camera.png Binary files differnew file mode 100644 index 000000000..491547189 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/ic_switch_camera.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked.png Binary files differnew file mode 100644 index 000000000..799a78ebb --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked_disabled.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked_disabled.png Binary files differnew file mode 100644 index 000000000..4d5e03320 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked_pressed.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked_pressed.png Binary files differnew file mode 100644 index 000000000..62cd1a477 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_checked_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_default.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_default.png Binary files differnew file mode 100644 index 000000000..c68ad909a --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_default.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_disabled.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_disabled.png Binary files differnew file mode 100644 index 000000000..e5c3fc48d --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_pressed.png b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_pressed.png Binary files differnew file mode 100644 index 000000000..583c3de82 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xhdpi/video_button_bg_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/ic_switch_camera.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/ic_switch_camera.png Binary files differnew file mode 100644 index 000000000..19a9344e9 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/ic_switch_camera.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked.png Binary files differnew file mode 100644 index 000000000..5a7702bbc --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked_disabled.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked_disabled.png Binary files differnew file mode 100644 index 000000000..a0be8d17d --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked_pressed.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked_pressed.png Binary files differnew file mode 100644 index 000000000..5671bfa06 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_checked_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_default.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_default.png Binary files differnew file mode 100644 index 000000000..527b3c47e --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_default.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_disabled.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_disabled.png Binary files differnew file mode 100644 index 000000000..996185890 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_disabled.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_pressed.png b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_pressed.png Binary files differnew file mode 100644 index 000000000..56295b10f --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxhdpi/video_button_bg_pressed.png diff --git a/java/com/android/incallui/video/impl/res/drawable-xxxhdpi/ic_switch_camera.png b/java/com/android/incallui/video/impl/res/drawable-xxxhdpi/ic_switch_camera.png Binary files differnew file mode 100644 index 000000000..529c0a4d5 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable-xxxhdpi/ic_switch_camera.png diff --git a/java/com/android/incallui/video/impl/res/drawable/videocall_background_circle_white.xml b/java/com/android/incallui/video/impl/res/drawable/videocall_background_circle_white.xml new file mode 100644 index 000000000..ee514c776 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable/videocall_background_circle_white.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#80888888"> + <item> + <shape + android:shape="oval"> + <solid android:color="@color/incall_button_white"/> + </shape> + </item> +</ripple> diff --git a/java/com/android/incallui/video/impl/res/drawable/videocall_video_button_background.xml b/java/com/android/incallui/video/impl/res/drawable/videocall_video_button_background.xml new file mode 100644 index 000000000..5e4841327 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/drawable/videocall_video_button_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/incall_button_ripple"> + <item android:id="@android:id/mask"> + <inset android:inset="5dp"> + <shape android:shape="oval"> + <solid android:color="@android:color/white"/> + </shape> + </inset> + </item> + <item> + <selector> + <item + android:drawable="@drawable/video_button_bg_checked_pressed" + android:state_checked="true" + android:state_pressed="true"/> + <item + android:drawable="@drawable/video_button_bg_checked" + android:state_checked="true"/> + <item + android:drawable="@drawable/video_button_bg_pressed" + android:state_pressed="true"/> + <item + android:drawable="@drawable/video_button_bg_default"/> + </selector> + </item> +</ripple> diff --git a/java/com/android/incallui/video/impl/res/layout-v21/switch_camera_button.xml b/java/com/android/incallui/video/impl/res/layout-v21/switch_camera_button.xml new file mode 100644 index 000000000..1fb1bb088 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout-v21/switch_camera_button.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<ImageButton xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/videocall_switch_video" + style="@style/Incall.Button.VideoCall" + android:contentDescription="@string/incall_content_description_swap_video" + android:src="@drawable/front_back_switch_button_animation"/> diff --git a/java/com/android/incallui/video/impl/res/layout/frag_videocall.xml b/java/com/android/incallui/video/impl/res/layout/frag_videocall.xml new file mode 100644 index 000000000..dc663dda1 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/frag_videocall.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + android:orientation="vertical"> + + <TextureView + android:id="@+id/videocall_video_remote" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:importantForAccessibility="no"/> + + <ImageView + android:id="@+id/videocall_remote_off_blurred_image_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:scaleType="fitCenter"/> + + <TextView + android:gravity="center" + android:id="@+id/videocall_remote_video_off" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:accessibilityTraversalBefore="@+id/videocall_speaker_button" + android:drawablePadding="8dp" + android:drawableTop="@drawable/quantum_ic_videocam_off_white_36" + android:padding="64dp" + android:text="@string/videocall_remote_video_off" + android:textAppearance="@style/Dialer.Incall.TextAppearance" + android:visibility="gone" + tools:visibility="visible"/> + + <View + android:id="@+id/videocall_fullscreen_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:background="@color/videocall_overlay_background_color"/> + + <TextureView + android:id="@+id/videocall_video_preview" + android:layout_width="@dimen/videocall_preview_width" + android:layout_height="@dimen/videocall_preview_height" + android:layout_marginBottom="@dimen/videocall_preview_margin_bottom" + android:layout_marginStart="@dimen/videocall_preview_margin_start" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:importantForAccessibility="no"/> + + <ImageView + android:id="@+id/videocall_preview_off_blurred_image_view" + android:layout_width="@dimen/videocall_preview_width" + android:layout_height="@dimen/videocall_preview_height" + android:layout_marginBottom="@dimen/videocall_preview_margin_bottom" + android:layout_marginStart="@dimen/videocall_preview_margin_start" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:scaleType="center"/> + + <View + android:id="@+id/videocall_green_screen_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:background="@color/videocall_overlay_background_color"/> + + <ImageView + android:id="@+id/videocall_video_preview_off_overlay" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/videocall_video_preview" + android:layout_alignLeft="@+id/videocall_video_preview" + android:layout_alignRight="@+id/videocall_video_preview" + android:layout_alignTop="@+id/videocall_video_preview" + android:scaleType="center" + android:src="@drawable/quantum_ic_videocam_off_white_36" + android:visibility="gone" + android:importantForAccessibility="no" + tools:visibility="visible"/> + + <ImageView + android:id="@+id/videocall_video_preview_mute_overlay" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_alignBottom="@+id/videocall_video_preview" + android:layout_alignRight="@+id/videocall_video_preview" + android:background="@drawable/videocall_background_circle_white" + android:contentDescription="@string/incall_content_description_muted" + android:scaleType="center" + android:src="@drawable/quantum_ic_mic_off_black_24" + android:visibility="gone" + tools:visibility="visible"/> + + <include + layout="@layout/videocall_controls" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + <FrameLayout + android:id="@+id/videocall_on_hold_banner" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true"/> + +</RelativeLayout> diff --git a/java/com/android/incallui/video/impl/res/layout/frag_videocall_land.xml b/java/com/android/incallui/video/impl/res/layout/frag_videocall_land.xml new file mode 100644 index 000000000..2353deea1 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/frag_videocall_land.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black"> + + <TextureView + android:id="@+id/videocall_video_remote" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:importantForAccessibility="no"/> + + <ImageView + android:id="@+id/videocall_remote_off_blurred_image_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:scaleType="fitCenter"/> + + <TextView + android:gravity="center" + android:id="@+id/videocall_remote_video_off" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:accessibilityTraversalBefore="@+id/videocall_speaker_button" + android:drawablePadding="8dp" + android:drawableTop="@drawable/quantum_ic_videocam_off_white_36" + android:padding="64dp" + android:text="@string/videocall_remote_video_off" + android:textAppearance="@style/Dialer.Incall.TextAppearance" + android:visibility="gone" + tools:visibility="visible"/> + + <View + android:id="@+id/videocall_fullscreen_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:background="@color/videocall_overlay_background_color"/> + + <TextureView + android:id="@+id/videocall_video_preview" + android:layout_width="@dimen/videocall_preview_width" + android:layout_height="@dimen/videocall_preview_height" + android:layout_marginEnd="@dimen/videocall_preview_margin_end" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:importantForAccessibility="no"/> + + <ImageView + android:id="@+id/videocall_preview_off_blurred_image_view" + android:layout_width="@dimen/videocall_preview_width" + android:layout_height="@dimen/videocall_preview_height" + android:layout_marginEnd="@dimen/videocall_preview_margin_end" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:scaleType="center"/> + + <View + android:id="@+id/videocall_green_screen_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:background="@color/videocall_overlay_background_color"/> + + <ImageView + android:id="@+id/videocall_video_preview_off_overlay" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/videocall_video_preview" + android:layout_alignLeft="@+id/videocall_video_preview" + android:layout_alignRight="@+id/videocall_video_preview" + android:layout_alignTop="@+id/videocall_video_preview" + android:scaleType="center" + android:src="@drawable/quantum_ic_videocam_off_white_36" + android:visibility="gone" + android:importantForAccessibility="no" + tools:visibility="visible"/> + + <ImageView + android:id="@+id/videocall_video_preview_mute_overlay" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_alignBottom="@+id/videocall_video_preview" + android:layout_alignRight="@+id/videocall_video_preview" + android:background="@drawable/videocall_background_circle_white" + android:contentDescription="@string/incall_content_description_muted" + android:scaleType="center" + android:src="@drawable/quantum_ic_mic_off_black_24" + android:visibility="gone" + tools:visibility="visible"/> + + <include + layout="@layout/videocall_controls_land" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + <FrameLayout + android:id="@+id/videocall_on_hold_banner" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true"/> + +</RelativeLayout> diff --git a/java/com/android/incallui/video/impl/res/layout/switch_camera_button.xml b/java/com/android/incallui/video/impl/res/layout/switch_camera_button.xml new file mode 100644 index 000000000..87c2e1b6c --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/switch_camera_button.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<ImageButton xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/videocall_switch_video" + style="@style/Incall.Button.VideoCall" + android:contentDescription="@string/incall_content_description_swap_video" + android:src="@drawable/ic_switch_camera"/> diff --git a/java/com/android/incallui/video/impl/res/layout/video_contact_grid.xml b/java/com/android/incallui/video/impl/res/layout/video_contact_grid.xml new file mode 100644 index 000000000..ad984f36e --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/video_contact_grid.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:paddingTop="16dp" + android:orientation="vertical"> + + <include + layout="@layout/incall_contactgrid_top_row" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <!-- We have to keep deprecated singleLine to allow long text being truncated with ellipses. + b/31396406 --> + <com.android.incallui.autoresizetext.AutoResizeTextView + android:id="@id/contactgrid_contact_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="@style/Dialer.Incall.TextAppearance.Large" + app:autoResizeText_minTextSize="28sp" + tools:text="Jake Peralta" + tools:ignore="Deprecated"/> + + <include + layout="@layout/incall_contactgrid_bottom_row" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</LinearLayout> diff --git a/java/com/android/incallui/video/impl/res/layout/videocall_controls.xml b/java/com/android/incallui/video/impl/res/layout/videocall_controls.xml new file mode 100644 index 000000000..b3141bdf3 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/videocall_controls.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/videocall_video_controls_container" + android:fitsSystemWindows="true" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include + android:id="@+id/incall_contact_grid" + layout="@layout/video_contact_grid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp"/> + + <!-- This placeholder matches the position of the preview UI and is used to + anchor video buttons. This is needed in greenscreen mode when the + preview is fullscreen but we want the controls to be positioned as + normal. --> + <Space + android:id="@+id/videocall_video_preview_placeholder" + android:layout_width="@dimen/videocall_preview_width" + android:layout_height="@dimen/videocall_preview_height" + android:layout_marginBottom="@dimen/videocall_preview_margin_bottom" + android:layout_marginStart="@dimen/videocall_preview_margin_start" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:visibility="invisible"/> + + <LinearLayout + android:id="@+id/videocall_video_controls" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_above="@+id/videocall_video_preview_placeholder" + android:layout_alignEnd="@+id/videocall_video_preview_placeholder" + android:layout_alignStart="@+id/videocall_video_preview_placeholder" + android:gravity="center_horizontal" + android:orientation="vertical" + android:visibility="invisible" + tools:visibility="visible"> + <com.android.incallui.video.impl.CheckableImageButton + android:id="@+id/videocall_speaker_button" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginBottom="@dimen/videocall_button_spacing" + android:checked="true" + android:src="@drawable/quantum_ic_volume_up_white_36" + app:contentDescriptionChecked="@string/incall_content_description_speaker" + app:contentDescriptionUnchecked="@string/incall_content_description_earpiece" + /> + <com.android.incallui.video.impl.CheckableImageButton + android:id="@+id/videocall_mute_button" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginBottom="@dimen/videocall_button_spacing" + android:src="@drawable/quantum_ic_mic_off_white_36" + app:contentDescriptionChecked="@string/incall_content_description_muted" + app:contentDescriptionUnchecked="@string/incall_content_description_unmuted" + /> + <com.android.incallui.video.impl.CheckableImageButton + android:id="@+id/videocall_mute_video" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginBottom="@dimen/videocall_button_spacing" + android:src="@drawable/quantum_ic_videocam_off_white_36" + app:contentDescriptionChecked="@string/incall_content_description_video_off" + app:contentDescriptionUnchecked="@string/incall_content_description_video_on" + /> + <include + layout="@layout/switch_camera_button" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginBottom="@dimen/videocall_button_spacing"/> + </LinearLayout> + + <FrameLayout + android:id="@+id/videocall_switch_controls" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="36dp" + android:layout_marginEnd="24dp" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true"> + <ImageButton + android:id="@+id/videocall_switch_on_hold" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:contentDescription="@string/incall_content_description_swap_calls" + android:src="@drawable/quantum_ic_swap_calls_white_36" + android:visibility="gone" + tools:visibility="visible" + /> + </FrameLayout> + + <ImageButton + android:id="@+id/videocall_end_call" + style="@style/Incall.Button.End" + android:layout_marginBottom="36dp" + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" + android:contentDescription="@string/incall_content_description_end_call" + android:visibility="visible"/> + +</RelativeLayout> diff --git a/java/com/android/incallui/video/impl/res/layout/videocall_controls_land.xml b/java/com/android/incallui/video/impl/res/layout/videocall_controls_land.xml new file mode 100644 index 000000000..d71b3c00e --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/videocall_controls_land.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/videocall_video_controls_container" + android:fitsSystemWindows="true" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include + android:id="@+id/incall_contact_grid" + layout="@layout/video_contact_grid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp"/> + + <!-- This placeholder matches the position of the preview UI and is used to + anchor video buttons. This is needed in greenscreen mode when the + preview is fullscreen but we want the controls to be positioned as + normal. --> + <Space + android:id="@+id/videocall_video_preview_placeholder" + android:layout_width="@dimen/videocall_preview_width" + android:layout_height="@dimen/videocall_preview_height" + android:layout_marginEnd="@dimen/videocall_preview_margin_end" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:visibility="invisible"/> + + <LinearLayout + android:id="@+id/videocall_video_controls" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/videocall_video_preview_placeholder" + android:layout_alignTop="@+id/videocall_video_preview_placeholder" + android:layout_toStartOf="@+id/videocall_video_preview_placeholder" + android:gravity="center_horizontal" + android:orientation="horizontal" + android:visibility="invisible" + tools:visibility="visible"> + <com.android.incallui.video.impl.CheckableImageButton + android:id="@+id/videocall_speaker_button" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginEnd="24dp" + android:checked="true" + android:src="@drawable/quantum_ic_volume_up_white_36" + app:contentDescriptionChecked="@string/incall_content_description_speaker" + app:contentDescriptionUnchecked="@string/incall_content_description_earpiece" + /> + <com.android.incallui.video.impl.CheckableImageButton + android:id="@+id/videocall_mute_button" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginEnd="24dp" + android:scaleType="center" + android:src="@drawable/quantum_ic_mic_off_white_36" + app:contentDescriptionChecked="@string/incall_content_description_muted" + app:contentDescriptionUnchecked="@string/incall_content_description_unmuted" + /> + <com.android.incallui.video.impl.CheckableImageButton + android:id="@+id/videocall_mute_video" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginEnd="24dp" + android:scaleType="center" + android:src="@drawable/quantum_ic_videocam_off_white_36" + app:contentDescriptionChecked="@string/incall_content_description_video_off" + app:contentDescriptionUnchecked="@string/incall_content_description_video_on" + /> + <include + layout="@layout/switch_camera_button" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:layout_marginEnd="24dp"/> + </LinearLayout> + + <FrameLayout + android:id="@+id/videocall_switch_controls" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="36dp" + android:layout_marginEnd="36dp" + android:layout_alignParentEnd="true" + android:layout_alignParentTop="true"> + <ImageButton + android:id="@+id/videocall_switch_on_hold" + style="@style/Incall.Button.VideoCall" + android:layout_width="@dimen/videocall_button_size" + android:layout_height="@dimen/videocall_button_size" + android:contentDescription="@string/incall_content_description_swap_calls" + android:src="@drawable/quantum_ic_swap_calls_white_36" + android:visibility="gone" + tools:visibility="visible" + /> + </FrameLayout> + + <ImageButton + android:id="@+id/videocall_end_call" + style="@style/Incall.Button.End" + android:layout_marginEnd="36dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:contentDescription="@string/incall_content_description_end_call" + android:visibility="visible" + tools:visibility="visible"/> + +</RelativeLayout> diff --git a/java/com/android/incallui/video/impl/res/values-h580dp/dimens.xml b/java/com/android/incallui/video/impl/res/values-h580dp/dimens.xml new file mode 100644 index 000000000..b1a86a0fa --- /dev/null +++ b/java/com/android/incallui/video/impl/res/values-h580dp/dimens.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="videocall_button_spacing">16dp</dimen> + <dimen name="videocall_button_size">72dp</dimen> + <dimen name="videocall_preview_width">88dp</dimen> + <dimen name="videocall_preview_height">88dp</dimen> +</resources> diff --git a/java/com/android/incallui/video/impl/res/values-w460dp/dimens.xml b/java/com/android/incallui/video/impl/res/values-w460dp/dimens.xml new file mode 100644 index 000000000..b1a86a0fa --- /dev/null +++ b/java/com/android/incallui/video/impl/res/values-w460dp/dimens.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="videocall_button_spacing">16dp</dimen> + <dimen name="videocall_button_size">72dp</dimen> + <dimen name="videocall_preview_width">88dp</dimen> + <dimen name="videocall_preview_height">88dp</dimen> +</resources> diff --git a/java/com/android/incallui/video/impl/res/values/attrs.xml b/java/com/android/incallui/video/impl/res/values/attrs.xml new file mode 100644 index 000000000..e4cd8af89 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/values/attrs.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <declare-styleable name="CheckableImageButton"> + <attr name="android:checked"/> + <attr name="contentDescriptionChecked" format="reference|string"/> + <attr name="contentDescriptionUnchecked" format="reference|string"/> + </declare-styleable> +</resources> diff --git a/java/com/android/incallui/video/impl/res/values/dimens.xml b/java/com/android/incallui/video/impl/res/values/dimens.xml new file mode 100644 index 000000000..45860036f --- /dev/null +++ b/java/com/android/incallui/video/impl/res/values/dimens.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="videocall_preview_width">72dp</dimen> + <dimen name="videocall_preview_height">72dp</dimen> + <dimen name="videocall_preview_margin_bottom">24dp</dimen> + <dimen name="videocall_preview_margin_start">24dp</dimen> + <dimen name="videocall_preview_margin_end">24dp</dimen> + <dimen name="videocall_button_spacing">8dp</dimen> + <dimen name="videocall_button_size">60dp</dimen> +</resources> diff --git a/java/com/android/incallui/video/impl/res/values/strings.xml b/java/com/android/incallui/video/impl/res/values/strings.xml new file mode 100644 index 000000000..2b72b8004 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/values/strings.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <!-- Text indicates the video from remote party is off. [CHAR LIMIT=40] --> + <string name="videocall_remote_video_off">Their video is off</string> + + <!-- Text indicates the video from remote party is on. [CHAR LIMIT=40] --> + <string name="videocall_remote_video_on">Their video is on</string> + + <!-- Text indicates the call is held by remote party. [CHAR LIMIT=20] --> + <string name="videocall_remotely_held">Call on hold</string> + + <!-- Text indicates the call is resumed from held by remote party. [CHAR LIMIT=20] --> + <string name="videocall_remotely_resumed">Call resumed</string> + + <!-- Title of dialog to ask user for camera permission. [CHAR LIMIT=30] --> + <string name="camera_permission_dialog_title">Allow video?</string> + + <!-- Message of dialog to ask user for camera permission. [CHAR LIMIT=100] --> + <string name="camera_permission_dialog_message">The Phone app wants to use your camera for video calls.</string> + + <!-- Text of button to be confirmed for camera permission by user. [CHAR LIMIT=20] --> + <string name="camera_permission_dialog_positive_button">Allow</string> + + <!-- Text of button to be declined for camera permission by user. [CHAR LIMIT=20] --> + <string name="camera_permission_dialog_negative_button">Deny</string> + +</resources> diff --git a/java/com/android/incallui/video/impl/res/values/styles.xml b/java/com/android/incallui/video/impl/res/values/styles.xml new file mode 100644 index 000000000..b94400875 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/values/styles.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="Incall.Button.VideoCall" parent="Widget.AppCompat.ImageButton"> + <item name="android:background">@drawable/videocall_video_button_background</item> + <item name="android:scaleType">center</item> + <item name="android:tint">@color/videocall_button_icon_tint</item> + <item name="android:tintMode">src_atop</item> + <item name="android:stateListAnimator">@animator/disabled_alpha</item> + </style> +</resources> |