summaryrefslogtreecommitdiff
path: root/java/com/android/incallui
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/incallui')
-rw-r--r--java/com/android/incallui/InCallActivity.java6
-rw-r--r--java/com/android/incallui/InCallPresenter.java2
-rw-r--r--java/com/android/incallui/answer/bindings/AnswerBindings.java8
-rw-r--r--java/com/android/incallui/answer/impl/AnswerFragment.java19
-rw-r--r--java/com/android/incallui/answer/impl/FixedAspectSurfaceView.java86
-rw-r--r--java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java268
-rw-r--r--java/com/android/incallui/answer/impl/res/layout/fragment_incoming_call.xml20
-rw-r--r--java/com/android/incallui/answer/impl/res/values-land/dimens.xml20
-rw-r--r--java/com/android/incallui/answer/impl/res/values/attrs.xml26
-rw-r--r--java/com/android/incallui/answer/impl/res/values/dimens.xml3
-rw-r--r--java/com/android/incallui/calllocation/CallLocationComponent.java20
-rw-r--r--java/com/android/incallui/calllocation/impl/HttpFetcher.java2
-rw-r--r--java/com/android/incallui/calllocation/stub/StubCallLocationModule.java6
-rw-r--r--java/com/android/incallui/incall/impl/AndroidManifest.xml4
-rw-r--r--java/com/android/incallui/incall/impl/InCallFragment.java29
-rw-r--r--java/com/android/incallui/incall/impl/InCallPaginator.java210
-rw-r--r--java/com/android/incallui/incall/impl/LockableViewPager.java46
-rw-r--r--java/com/android/incallui/incall/impl/res/layout/frag_incall_voice.xml20
-rw-r--r--java/com/android/incallui/incall/impl/res/values/dimens.xml5
-rw-r--r--java/com/android/incallui/incall/impl/res/values/styles.xml3
-rw-r--r--java/com/android/incallui/maps/MapsComponent.java21
-rw-r--r--java/com/android/incallui/maps/stub/StubMapsModule.java2
-rw-r--r--java/com/android/incallui/res/values-uz/strings.xml2
-rw-r--r--java/com/android/incallui/videotech/VideoTech.java6
-rw-r--r--java/com/android/incallui/videotech/empty/EmptyVideoTech.java5
-rw-r--r--java/com/android/incallui/videotech/ims/ImsVideoTech.java7
-rw-r--r--java/com/android/incallui/videotech/rcs/RcsVideoShare.java5
27 files changed, 777 insertions, 74 deletions
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 7c4394872..395829b80 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -625,7 +625,11 @@ public class InCallActivity extends TransactionSafeFragmentActivity
// Show a new answer screen.
AnswerScreen answerScreen =
- AnswerBindings.createAnswerScreen(call.getId(), call.isVideoCall(), isVideoUpgradeRequest);
+ AnswerBindings.createAnswerScreen(
+ call.getId(),
+ call.isVideoCall(),
+ isVideoUpgradeRequest,
+ call.getVideoTech().isSelfManagedCamera());
transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), TAG_ANSWER_SCREEN);
Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index 0f3982ce4..ea7bc9cc3 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -758,7 +758,7 @@ public class InCallPresenter implements CallList.Listener {
}
if (!call.getLogState().isIncoming && !mCallList.hasLiveCall()) {
- PostCall.onCallDisconnected(mContext, call.getNumber(), call.getConnectTimeMillis());
+ PostCall.onCallDisconnected(mContext, call.getNumber(), call.getTimeAddedMs());
}
}
diff --git a/java/com/android/incallui/answer/bindings/AnswerBindings.java b/java/com/android/incallui/answer/bindings/AnswerBindings.java
index 442e207a0..8be283990 100644
--- a/java/com/android/incallui/answer/bindings/AnswerBindings.java
+++ b/java/com/android/incallui/answer/bindings/AnswerBindings.java
@@ -23,7 +23,11 @@ import com.android.incallui.answer.protocol.AnswerScreen;
public class AnswerBindings {
public static AnswerScreen createAnswerScreen(
- String callId, boolean isVideoCall, boolean isVideoUpgradeRequest) {
- return AnswerFragment.newInstance(callId, isVideoCall, isVideoUpgradeRequest);
+ String callId,
+ boolean isVideoCall,
+ boolean isVideoUpgradeRequest,
+ boolean isSelfManagedCamera) {
+ return AnswerFragment.newInstance(
+ callId, isVideoCall, isVideoUpgradeRequest, isSelfManagedCamera);
}
}
diff --git a/java/com/android/incallui/answer/impl/AnswerFragment.java b/java/com/android/incallui/answer/impl/AnswerFragment.java
index 6874daea3..a6174686c 100644
--- a/java/com/android/incallui/answer/impl/AnswerFragment.java
+++ b/java/com/android/incallui/answer/impl/AnswerFragment.java
@@ -106,6 +106,9 @@ public class AnswerFragment extends Fragment
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static final String ARG_IS_VIDEO_UPGRADE_REQUEST = "is_video_upgrade_request";
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String ARG_IS_SELF_MANAGED_CAMERA = "is_self_managed_camera";
+
private static final String STATE_HAS_ANIMATED_ENTRY = "hasAnimated";
private static final int HINT_SECONDARY_SHOW_DURATION_MILLIS = 5000;
@@ -288,11 +291,15 @@ public class AnswerFragment extends Fragment
}
public static AnswerFragment newInstance(
- String callId, boolean isVideoCall, boolean isVideoUpgradeRequest) {
+ String callId,
+ boolean isVideoCall,
+ boolean isVideoUpgradeRequest,
+ boolean isSelfManagedCamera) {
Bundle bundle = new Bundle();
bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId));
bundle.putBoolean(ARG_IS_VIDEO_CALL, isVideoCall);
bundle.putBoolean(ARG_IS_VIDEO_UPGRADE_REQUEST, isVideoUpgradeRequest);
+ bundle.putBoolean(ARG_IS_SELF_MANAGED_CAMERA, isSelfManagedCamera);
AnswerFragment instance = new AnswerFragment();
instance.setArguments(bundle);
@@ -620,7 +627,11 @@ public class AnswerFragment extends Fragment
view.setSystemUiVisibility(flags);
if (isVideoCall() || isVideoUpgradeRequest()) {
if (VideoUtils.hasCameraPermissionAndAllowedByUser(getContext())) {
- answerVideoCallScreen = new AnswerVideoCallScreen(getCallId(), this, view);
+ if (isSelfManagedCamera()) {
+ answerVideoCallScreen = new SelfManagedAnswerVideoCallScreen(getCallId(), this, view);
+ } else {
+ answerVideoCallScreen = new AnswerVideoCallScreen(getCallId(), this, view);
+ }
} else {
view.findViewById(R.id.videocall_video_off).setVisibility(View.VISIBLE);
}
@@ -718,6 +729,10 @@ public class AnswerFragment extends Fragment
return getArguments().getBoolean(ARG_IS_VIDEO_CALL);
}
+ public boolean isSelfManagedCamera() {
+ return getArguments().getBoolean(ARG_IS_SELF_MANAGED_CAMERA);
+ }
+
@Override
public void onAnswerProgressUpdate(@FloatRange(from = -1f, to = 1f) float answerProgress) {
// Don't fade the window background for call waiting or video upgrades. Fading the background
diff --git a/java/com/android/incallui/answer/impl/FixedAspectSurfaceView.java b/java/com/android/incallui/answer/impl/FixedAspectSurfaceView.java
new file mode 100644
index 000000000..ad7d94d95
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/FixedAspectSurfaceView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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.answer.impl;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.SurfaceView;
+import android.view.View;
+import com.android.dialer.common.Assert;
+
+/**
+ * A SurfaceView that maintains its aspect ratio to be a desired target value.
+ *
+ * <p>The FixedAspectSurfaceView will not be able to maintain the requested aspect ratio if both the
+ * width and the height are exactly determined by the layout. To avoid this, ensure that either the
+ * height or the width is adjustable by the view; for example, by setting the layout parameters to
+ * be WRAP_CONTENT for the dimension that is best adjusted to maintain the aspect ratio.
+ */
+public class FixedAspectSurfaceView extends SurfaceView {
+
+ /** Desired width/height ratio */
+ private float mAspectRatio;
+
+ private final boolean scaleWidth;
+ private final boolean scaleHeight;
+
+ public FixedAspectSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // Get initial aspect ratio from custom attributes
+ TypedArray a =
+ context.getTheme().obtainStyledAttributes(attrs, R.styleable.FixedAspectSurfaceView, 0, 0);
+ scaleHeight = a.getBoolean(R.styleable.FixedAspectSurfaceView_scaleHeight, false);
+ scaleWidth = a.getBoolean(R.styleable.FixedAspectSurfaceView_scaleWidth, false);
+ Assert.checkArgument(scaleHeight != scaleWidth, "Must either scale width or height");
+ setAspectRatio(a.getFloat(R.styleable.FixedAspectSurfaceView_aspectRatio, 1.f));
+ a.recycle();
+ }
+
+ /**
+ * Set the desired aspect ratio for this view.
+ *
+ * @param aspect the desired width/height ratio in the current UI orientation. Must be a positive
+ * value.
+ */
+ public void setAspectRatio(float aspect) {
+ Assert.checkArgument(aspect >= 0, "Aspect ratio must be positive");
+ mAspectRatio = aspect;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ // Do the scaling
+ if (scaleWidth) {
+ width = (int) (height * mAspectRatio);
+ } else if (scaleHeight) {
+ height = (int) (width / mAspectRatio);
+ }
+
+ // Override width/height if needed for EXACTLY and AT_MOST specs
+ width = View.resolveSizeAndState(width, widthMeasureSpec, 0);
+ height = View.resolveSizeAndState(height, heightMeasureSpec, 0);
+
+ // Finally set the calculated dimensions
+ setMeasuredDimension(width, height);
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java b/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java
new file mode 100644
index 000000000..522d77235
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 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.answer.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.StateCallback;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.Size;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.incallui.video.protocol.VideoCallScreen;
+import java.util.Arrays;
+
+/**
+ * Shows the local preview for the incoming video call or video upgrade request. This class is used
+ * for RCS Video Share where we need to open the camera preview ourselves. For IMS Video the camera
+ * is managed by the modem, see {@link AnswerVideoCallScreen}.
+ */
+public class SelfManagedAnswerVideoCallScreen extends StateCallback implements VideoCallScreen {
+
+ private static final int MAX_WIDTH = 1920;
+ private static final float ASPECT_TOLERANCE = 0.1f;
+ private static final float TARGET_ASPECT = 16.f / 9.f;
+
+ @NonNull private final String callId;
+ @NonNull private final Fragment fragment;
+ @NonNull private final FixedAspectSurfaceView surfaceView;
+ private final Context context;
+
+ private String cameraId;
+ private CameraDevice camera;
+ private CaptureRequest.Builder captureRequestBuilder;
+
+ public SelfManagedAnswerVideoCallScreen(
+ @NonNull String callId, @NonNull Fragment fragment, @NonNull View view) {
+ this.callId = Assert.isNotNull(callId);
+ this.fragment = Assert.isNotNull(fragment);
+ this.context = Assert.isNotNull(fragment.getContext());
+
+ surfaceView =
+ Assert.isNotNull(
+ (FixedAspectSurfaceView) view.findViewById(R.id.incoming_preview_surface_view));
+ surfaceView.setVisibility(View.VISIBLE);
+ view.findViewById(R.id.incoming_preview_texture_view_overlay).setVisibility(View.VISIBLE);
+ view.setBackgroundColor(0xff000000);
+ }
+
+ @Override
+ public void onVideoScreenStart() {
+ openCamera();
+ }
+
+ @Override
+ public void onVideoScreenStop() {
+ closeCamera();
+ }
+
+ @Override
+ public void showVideoViews(
+ boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld) {}
+
+ @Override
+ public void onLocalVideoDimensionsChanged() {}
+
+ @Override
+ public void onLocalVideoOrientationChanged() {}
+
+ @Override
+ public void onRemoteVideoDimensionsChanged() {}
+
+ @Override
+ public void updateFullscreenAndGreenScreenMode(
+ boolean shouldShowFullscreen, boolean shouldShowGreenScreen) {}
+
+ @Override
+ public Fragment getVideoCallScreenFragment() {
+ return fragment;
+ }
+
+ @Override
+ public String getCallId() {
+ return callId;
+ }
+
+ /**
+ * Opens the first front facing camera on the device into a {@link SurfaceView} while preserving
+ * aspect ratio.
+ */
+ private void openCamera() {
+ CameraManager manager = context.getSystemService(CameraManager.class);
+
+ StreamConfigurationMap configMap = getFrontFacingCameraSizes(manager);
+ if (configMap == null) {
+ return;
+ }
+
+ Size previewSize = getOptimalSize(configMap.getOutputSizes(SurfaceHolder.class));
+ LogUtil.i("SelfManagedAnswerVideoCallScreen.openCamera", "Optimal size: " + previewSize);
+ float outputAspect = (float) previewSize.getWidth() / previewSize.getHeight();
+ surfaceView.setAspectRatio(outputAspect);
+ surfaceView.getHolder().setFixedSize(previewSize.getWidth(), previewSize.getHeight());
+
+ try {
+ manager.openCamera(cameraId, this, null);
+ } catch (CameraAccessException e) {
+ LogUtil.e("SelfManagedAnswerVideoCallScreen.openCamera", "failed to open camera", e);
+ }
+ }
+
+ @Nullable
+ private StreamConfigurationMap getFrontFacingCameraSizes(CameraManager manager) {
+ String[] cameraIds;
+ try {
+ cameraIds = manager.getCameraIdList();
+ } catch (CameraAccessException e) {
+ LogUtil.e(
+ "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes",
+ "failed to get camera ids",
+ e);
+ return null;
+ }
+
+ for (String cameraId : cameraIds) {
+ CameraCharacteristics characteristics;
+ try {
+ characteristics = manager.getCameraCharacteristics(cameraId);
+ } catch (CameraAccessException e) {
+ LogUtil.e(
+ "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes",
+ "failed to get camera characteristics",
+ e);
+ continue;
+ }
+
+ if (characteristics.get(CameraCharacteristics.LENS_FACING)
+ != CameraCharacteristics.LENS_FACING_FRONT) {
+ continue;
+ }
+
+ StreamConfigurationMap configMap =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (configMap == null) {
+ continue;
+ }
+
+ this.cameraId = cameraId;
+ return configMap;
+ }
+ LogUtil.e(
+ "SelfManagedAnswerVideoCallScreen.getFrontFacingCameraSizes", "No valid configurations.");
+ return null;
+ }
+
+ /**
+ * Given an array of {@link Size}s, tries to find the largest Size such that the aspect ratio of
+ * the returned size is within {@code ASPECT_TOLERANCE} of {@code TARGET_ASPECT}. This is useful
+ * because it provides us with an adequate size/camera resolution that will experience the least
+ * stretching from our fullscreen UI that doesn't match any of the camera sizes.
+ */
+ private static Size getOptimalSize(Size[] outputSizes) {
+ Size bestCandidateSize = outputSizes[0];
+ float bestCandidateAspect =
+ (float) bestCandidateSize.getWidth() / bestCandidateSize.getHeight();
+
+ for (Size candidateSize : outputSizes) {
+ if (candidateSize.getWidth() < MAX_WIDTH) {
+ float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight();
+ boolean isGoodCandidateAspect =
+ Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
+ boolean isGoodOutputAspect =
+ Math.abs(bestCandidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
+
+ if ((isGoodCandidateAspect && !isGoodOutputAspect)
+ || candidateSize.getWidth() > bestCandidateSize.getWidth()) {
+ bestCandidateSize = candidateSize;
+ bestCandidateAspect = candidateAspect;
+ }
+ }
+ }
+ return bestCandidateSize;
+ }
+
+ @Override
+ public void onOpened(CameraDevice camera) {
+ LogUtil.i("SelfManagedAnswerVideoCallScreen.opOpened", "camera opened.");
+ this.camera = camera;
+ Surface surface = surfaceView.getHolder().getSurface();
+ try {
+ captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ captureRequestBuilder.addTarget(surface);
+ camera.createCaptureSession(Arrays.asList(surface), new CaptureSessionCallback(), null);
+ } catch (CameraAccessException e) {
+ LogUtil.e(
+ "SelfManagedAnswerVideoCallScreen.createCameraPreview", "failed to create preview", e);
+ }
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ closeCamera();
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ closeCamera();
+ }
+
+ private void closeCamera() {
+ if (camera != null) {
+ camera.close();
+ camera = null;
+ }
+ }
+
+ private class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
+
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
+ LogUtil.i(
+ "SelfManagedAnswerVideoCallScreen.onConfigured", "camera capture session configured.");
+ // The camera is already closed.
+ if (camera == null) {
+ return;
+ }
+
+ // When the session is ready, we start displaying the preview.
+ captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ try {
+ cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
+ } catch (CameraAccessException e) {
+ LogUtil.e("CaptureSessionCallback.onConfigured", "failed to configure", e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ LogUtil.e("CaptureSessionCallback.onConfigureFailed", "failed to configure");
+ }
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/res/layout/fragment_incoming_call.xml b/java/com/android/incallui/answer/impl/res/layout/fragment_incoming_call.xml
index aa153dd4b..042e7b82f 100644
--- a/java/com/android/incallui/answer/impl/res/layout/fragment_incoming_call.xml
+++ b/java/com/android/incallui/answer/impl/res/layout/fragment_incoming_call.xml
@@ -14,7 +14,6 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
<com.android.incallui.answer.impl.AffordanceHolderLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -27,11 +26,20 @@
android:keepScreenOn="true">
<TextureView
- android:id="@+id/incoming_preview_texture_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="no"
- android:visibility="gone"/>
+ android:id="@+id/incoming_preview_texture_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:visibility="gone"/>
+
+ <com.android.incallui.answer.impl.FixedAspectSurfaceView
+ android:id="@+id/incoming_preview_surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:visibility="gone"
+ app:scaleWidth="@bool/scale_width"
+ app:scaleHeight="@bool/scale_height"/>
<View
android:id="@+id/incoming_preview_texture_view_overlay"
diff --git a/java/com/android/incallui/answer/impl/res/values-land/dimens.xml b/java/com/android/incallui/answer/impl/res/values-land/dimens.xml
new file mode 100644
index 000000000..5e2a88ae9
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/res/values-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<resources>
+ <bool name="scale_width">false</bool>
+ <bool name="scale_height">true</bool>
+</resources>
diff --git a/java/com/android/incallui/answer/impl/res/values/attrs.xml b/java/com/android/incallui/answer/impl/res/values/attrs.xml
new file mode 100644
index 000000000..1086e1ca5
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/res/values/attrs.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+<resources>
+ <declare-styleable name="FixedAspectSurfaceView">
+ <attr name="aspectRatio" format="float" />
+ <attr name="scaleWidth" format="boolean"/>
+ <attr name="scaleHeight" format="boolean"/>
+ </declare-styleable>
+
+ <item name="match_parent" type="dimen">-1</item>
+ <item name="wrap_content" type="dimen">-2</item>
+</resources> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/res/values/dimens.xml b/java/com/android/incallui/answer/impl/res/values/dimens.xml
index 8329707a6..50aec0328 100644
--- a/java/com/android/incallui/answer/impl/res/values/dimens.xml
+++ b/java/com/android/incallui/answer/impl/res/values/dimens.xml
@@ -14,7 +14,6 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
<resources>
<dimen name="answer_contact_name_text_size">24sp</dimen>
<dimen name="answer_contact_name_min_size">24sp</dimen>
@@ -22,5 +21,7 @@
<dimen name="answer_avatar_size">0dp</dimen>
<dimen name="answer_importance_margin_bottom">0dp</dimen>
<bool name="answer_important_call_allowed">false</bool>
+ <bool name="scale_width">true</bool>
+ <bool name="scale_height">false</bool>
<integer name="answer_animate_entry_millis">1000</integer>
</resources>
diff --git a/java/com/android/incallui/calllocation/CallLocationComponent.java b/java/com/android/incallui/calllocation/CallLocationComponent.java
index 6b1faf299..46ca669db 100644
--- a/java/com/android/incallui/calllocation/CallLocationComponent.java
+++ b/java/com/android/incallui/calllocation/CallLocationComponent.java
@@ -17,26 +17,18 @@
package com.android.incallui.calllocation;
import android.content.Context;
+import com.android.dialer.inject.HasRootComponent;
import dagger.Subcomponent;
-import com.android.incallui.calllocation.stub.StubCallLocationModule;
/** Subcomponent that can be used to access the call location implementation. */
-public class CallLocationComponent {
- private static CallLocationComponent instance;
- private CallLocation callLocation;
+@Subcomponent
+public abstract class CallLocationComponent {
- public CallLocation getCallLocation(){
- if (callLocation == null) {
- callLocation = new StubCallLocationModule.StubCallLocation();
- }
- return callLocation;
- }
+ public abstract CallLocation getCallLocation();
public static CallLocationComponent get(Context context) {
- if (instance == null) {
- instance = new CallLocationComponent();
- }
- return instance;
+ return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
+ .callLocationComponent();
}
/** Used to refer to the root application component. */
diff --git a/java/com/android/incallui/calllocation/impl/HttpFetcher.java b/java/com/android/incallui/calllocation/impl/HttpFetcher.java
index c4aaa2257..7bfbaa6ef 100644
--- a/java/com/android/incallui/calllocation/impl/HttpFetcher.java
+++ b/java/com/android/incallui/calllocation/impl/HttpFetcher.java
@@ -223,6 +223,8 @@ public class HttpFetcher {
/**
* Lookup up url re-write rules from gServices and apply to the given url.
*
+ * <p>https://wiki.corp.google.com/twiki/bin/view/Main/AndroidGservices#URL_Rewriting_Rules
+ *
* @return The new url.
*/
private static URL reWriteUrl(Context context, String url) {
diff --git a/java/com/android/incallui/calllocation/stub/StubCallLocationModule.java b/java/com/android/incallui/calllocation/stub/StubCallLocationModule.java
index fc198c724..20460699a 100644
--- a/java/com/android/incallui/calllocation/stub/StubCallLocationModule.java
+++ b/java/com/android/incallui/calllocation/stub/StubCallLocationModule.java
@@ -32,9 +32,9 @@ public abstract class StubCallLocationModule {
@Binds
public abstract CallLocation bindCallLocation(StubCallLocation callLocation);
- static public class StubCallLocation implements CallLocation {
+ static class StubCallLocation implements CallLocation {
@Inject
- public StubCallLocation() {}
+ StubCallLocation() {}
@Override
public boolean canGetLocation(@NonNull Context context) {
@@ -44,7 +44,7 @@ public abstract class StubCallLocationModule {
@Override
@NonNull
public Fragment getLocationFragment(@NonNull Context context) {
- return null;
+ throw Assert.createUnsupportedOperationFailException();
}
@Override
diff --git a/java/com/android/incallui/incall/impl/AndroidManifest.xml b/java/com/android/incallui/incall/impl/AndroidManifest.xml
index a0e3110d8..3d646506d 100644
--- a/java/com/android/incallui/incall/impl/AndroidManifest.xml
+++ b/java/com/android/incallui/incall/impl/AndroidManifest.xml
@@ -1,3 +1 @@
-<manifest
- package="com.android.incall.incall.impl">
-</manifest>
+<manifest package="com.android.incallui.incall.impl"/>
diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java
index b6ae4902a..02e9b4add 100644
--- a/java/com/android/incallui/incall/impl/InCallFragment.java
+++ b/java/com/android/incallui/incall/impl/InCallFragment.java
@@ -25,11 +25,9 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
-import android.support.v4.view.ViewPager;
import android.telecom.CallAudioState;
import android.telephony.TelephonyManager;
import android.view.LayoutInflater;
@@ -74,8 +72,8 @@ public class InCallFragment extends Fragment
private List<ButtonController> buttonControllers = new ArrayList<>();
private View endCallButton;
- private TabLayout tabLayout;
- private ViewPager pager;
+ private InCallPaginator paginator;
+ private LockableViewPager pager;
private InCallPagerAdapter adapter;
private ContactGridManager contactGridManager;
private InCallScreenDelegate inCallScreenDelegate;
@@ -134,8 +132,8 @@ public class InCallFragment extends Fragment
getResources().getDimensionPixelSize(R.dimen.incall_avatar_size),
true /* showAnonymousAvatar */);
- tabLayout = (TabLayout) view.findViewById(R.id.incall_tab_dots);
- pager = (ViewPager) view.findViewById(R.id.incall_pager);
+ paginator = (InCallPaginator) view.findViewById(R.id.incall_paginator);
+ pager = (LockableViewPager) view.findViewById(R.id.incall_pager);
endCallButton = view.findViewById(R.id.incall_end_call);
endCallButton.setOnClickListener(this);
@@ -248,8 +246,8 @@ public class InCallFragment extends Fragment
}
if (adapter.getCount() > 1) {
- tabLayout.setVisibility(pager.getVisibility());
- tabLayout.setupWithViewPager(pager, true);
+ paginator.setVisibility(View.VISIBLE);
+ paginator.setupWithViewPager(pager);
if (!stateRestored) {
new Handler()
.postDelayed(
@@ -263,9 +261,9 @@ public class InCallFragment extends Fragment
}
},
2000);
+ } else {
+ paginator.setVisibility(View.GONE);
}
- } else {
- tabLayout.setVisibility(View.GONE);
}
}
@@ -428,8 +426,15 @@ public class InCallFragment extends Fragment
int visibility = numVisibleButtons == 0 ? View.GONE : View.VISIBLE;
pager.setVisibility(visibility);
- if (adapter != null && adapter.getCount() > 1) {
- tabLayout.setVisibility(visibility);
+ if (adapter != null
+ && adapter.getCount() > 1
+ && getResources().getInteger(R.integer.incall_num_rows) > 1) {
+ paginator.setVisibility(View.VISIBLE);
+ pager.setSwipingLocked(false);
+ } else {
+ paginator.setVisibility(View.GONE);
+ pager.setSwipingLocked(true);
+ pager.setCurrentItem(adapter.getButtonGridPosition());
}
}
diff --git a/java/com/android/incallui/incall/impl/InCallPaginator.java b/java/com/android/incallui/incall/impl/InCallPaginator.java
new file mode 100644
index 000000000..8ebbd76a2
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/InCallPaginator.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017 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.incall.impl;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.AttributeSet;
+import android.view.View;
+import com.android.dialer.common.Assert;
+
+/**
+ * This is the view class for incall paginator visible when a user has EC data attached to their
+ * call. It contains animation methods when the swipe gesture is performed.
+ */
+public class InCallPaginator extends View implements OnPageChangeListener {
+
+ private int dotRadius;
+ private int dotsSeparation;
+
+ private Paint activeDotPaintPortrait;
+ private Paint inactiveDotPaintPortrait;
+
+ private Path inactiveDotPath;
+ private ValueAnimator transitionAnimator;
+ private boolean useModeSwitchTransition;
+
+ private float progress;
+ private boolean toFirstPage;
+ private boolean pageChanged;
+
+ public InCallPaginator(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public InCallPaginator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ private void init(Context context) {
+ dotRadius = getResources().getDimensionPixelSize(R.dimen.paginator_dot_radius);
+ dotsSeparation = getResources().getDimensionPixelSize(R.dimen.paginator_dots_separation);
+
+ int activeDotColor = context.getColor(R.color.paginator_dot);
+ int inactiveDotColor = context.getColor(R.color.paginator_path);
+ activeDotPaintPortrait = new Paint(Paint.ANTI_ALIAS_FLAG);
+ activeDotPaintPortrait.setColor(activeDotColor);
+ inactiveDotPaintPortrait = new Paint(Paint.ANTI_ALIAS_FLAG);
+ inactiveDotPaintPortrait.setColor(inactiveDotColor);
+
+ inactiveDotPath = new Path();
+ transitionAnimator = ValueAnimator.ofFloat(0f, 1f);
+ transitionAnimator.setInterpolator(null);
+ transitionAnimator.setCurrentFraction(0f);
+ transitionAnimator.addUpdateListener(animation -> invalidate());
+ }
+
+ @VisibleForTesting
+ public void setProgress(float progress, boolean toFirstPage) {
+ this.progress = progress;
+ this.toFirstPage = toFirstPage;
+
+ // Ensure the dot transition keeps up with the swipe progress.
+ if (transitionAnimator.isStarted() && progress > transitionAnimator.getAnimatedFraction()) {
+ transitionAnimator.setCurrentFraction(progress);
+ }
+
+ invalidate();
+ }
+
+ private void startTransition() {
+ if (transitionAnimator.getAnimatedFraction() < 1f) {
+ transitionAnimator.setCurrentFraction(progress);
+ useModeSwitchTransition = false;
+ transitionAnimator.cancel();
+ transitionAnimator.start();
+ }
+ }
+
+ private void endTransition(boolean snapBack) {
+ if (transitionAnimator.getAnimatedFraction() > 0f) {
+ useModeSwitchTransition = !snapBack;
+ transitionAnimator.cancel();
+ transitionAnimator.reverse();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int centerX = getWidth() / 2;
+ int centerY = getHeight() / 2;
+
+ float transitionFraction = (float) transitionAnimator.getAnimatedValue();
+
+ // Draw the inactive "dots".
+ inactiveDotPath.reset();
+ if (useModeSwitchTransition) {
+ float trackWidth = 2 * dotRadius + transitionFraction * (2 * dotRadius + dotsSeparation);
+ float indicatorRadius = dotRadius * (1f - 2f * Math.min(transitionFraction, 0.5f));
+ float indicatorOffset = dotRadius + dotsSeparation / 2;
+ if (toFirstPage) {
+ float trackLeft = centerX - indicatorOffset - dotRadius;
+ inactiveDotPath.addRoundRect(
+ trackLeft,
+ centerY - dotRadius,
+ trackLeft + trackWidth,
+ centerY + dotRadius,
+ dotRadius,
+ dotRadius,
+ Path.Direction.CW);
+ inactiveDotPath.addCircle(
+ centerX + indicatorOffset, centerY, indicatorRadius, Path.Direction.CW);
+ } else {
+ float trackRight = centerX + indicatorOffset + dotRadius;
+ inactiveDotPath.addRoundRect(
+ trackRight - trackWidth,
+ centerY - dotRadius,
+ trackRight,
+ centerY + dotRadius,
+ dotRadius,
+ dotRadius,
+ Path.Direction.CW);
+ inactiveDotPath.addCircle(
+ centerX - indicatorOffset, centerY, indicatorRadius, Path.Direction.CW);
+ }
+ } else {
+ float centerOffset = dotsSeparation / 2f;
+ float innerOffset = centerOffset - transitionFraction * (dotRadius + centerOffset);
+ float outerOffset = 2f * dotRadius + centerOffset;
+ inactiveDotPath.addRoundRect(
+ centerX - outerOffset,
+ centerY - dotRadius,
+ centerX - innerOffset,
+ centerY + dotRadius,
+ dotRadius,
+ dotRadius,
+ Path.Direction.CW);
+ inactiveDotPath.addRoundRect(
+ centerX + innerOffset,
+ centerY - dotRadius,
+ centerX + outerOffset,
+ centerY + dotRadius,
+ dotRadius,
+ dotRadius,
+ Path.Direction.CW);
+ }
+ Paint inactivePaint = inactiveDotPaintPortrait;
+ canvas.drawPath(inactiveDotPath, inactivePaint);
+
+ // Draw the white active dot.
+ float activeDotOffset =
+ (toFirstPage ? 1f - 2f * progress : 2f * progress - 1f) * (dotRadius + dotsSeparation / 2);
+ Paint activePaint = activeDotPaintPortrait;
+ canvas.drawCircle(centerX + activeDotOffset, centerY, dotRadius, activePaint);
+ }
+
+ public void setupWithViewPager(ViewPager pager) {
+ Assert.checkArgument(pager.getAdapter().getCount() == 2, "Invalid page count.");
+ pager.addOnPageChangeListener(this);
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ setProgress(positionOffset, position != 0);
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ pageChanged = true;
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ switch (state) {
+ case ViewPager.SCROLL_STATE_IDLE:
+ endTransition(!pageChanged);
+ pageChanged = false;
+ break;
+ case ViewPager.SCROLL_STATE_DRAGGING:
+ startTransition();
+ break;
+ case ViewPager.SCROLL_STATE_SETTLING:
+ default:
+ break;
+ }
+ }
+}
diff --git a/java/com/android/incallui/incall/impl/LockableViewPager.java b/java/com/android/incallui/incall/impl/LockableViewPager.java
new file mode 100644
index 000000000..5b8b12609
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/LockableViewPager.java
@@ -0,0 +1,46 @@
+/*
+ * 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.incall.impl;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+/** {@link ViewPager} useful for disabled swiping between pages. */
+public class LockableViewPager extends ViewPager {
+
+ private boolean swipingLocked;
+
+ public LockableViewPager(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ }
+
+ public void setSwipingLocked(boolean swipingLocked) {
+ this.swipingLocked = swipingLocked;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
+ return !swipingLocked && super.onInterceptTouchEvent(motionEvent);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent motionEvent) {
+ return !swipingLocked && super.onTouchEvent(motionEvent);
+ }
+}
diff --git a/java/com/android/incallui/incall/impl/res/layout/frag_incall_voice.xml b/java/com/android/incallui/incall/impl/res/layout/frag_incall_voice.xml
index 9b950462c..e4bc942bb 100644
--- a/java/com/android/incallui/incall/impl/res/layout/frag_incall_voice.xml
+++ b/java/com/android/incallui/incall/impl/res/layout/frag_incall_voice.xml
@@ -60,23 +60,21 @@
android:layout_height="match_parent"/>
</LinearLayout>
- <android.support.v4.view.ViewPager
+ <com.android.incallui.incall.impl.LockableViewPager
android:id="@+id/incall_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_above="@+id/incall_tab_dots"
+ android:layout_above="@+id/incall_paginator"
android:layout_below="@+id/incall_contact_grid"
android:layout_centerHorizontal="true"/>
- <android.support.design.widget.TabLayout
- android:id="@+id/incall_tab_dots"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_above="@+id/incall_end_call"
- android:visibility="gone"
- app:tabBackground="@drawable/tab_selector"
- app:tabGravity="center"
- app:tabIndicatorHeight="0dp"/>
+ <com.android.incallui.incall.impl.InCallPaginator
+ android:id="@+id/incall_paginator"
+ android:layout_height="@dimen/paginator_height"
+ android:layout_width="@dimen/paginator_width"
+ android:layout_above="@+id/incall_end_call"
+ android:layout_centerHorizontal="true"
+ android:visibility="gone"/>
<FrameLayout
android:id="@+id/incall_dialpad_container"
diff --git a/java/com/android/incallui/incall/impl/res/values/dimens.xml b/java/com/android/incallui/incall/impl/res/values/dimens.xml
index 249788785..72602e3fc 100644
--- a/java/com/android/incallui/incall/impl/res/values/dimens.xml
+++ b/java/com/android/incallui/incall/impl/res/values/dimens.xml
@@ -14,4 +14,9 @@
<bool name="incall_dialpad_allowed">false</bool>
<integer name="incall_num_rows">0</integer>
+
+ <dimen name="paginator_dot_radius">5dp</dimen>
+ <dimen name="paginator_dots_separation">8dp</dimen>
+ <dimen name="paginator_height">38dp</dimen>
+ <dimen name="paginator_width">72dp</dimen>
</resources>
diff --git a/java/com/android/incallui/incall/impl/res/values/styles.xml b/java/com/android/incallui/incall/impl/res/values/styles.xml
index 2392574a3..a8cf2ddf1 100644
--- a/java/com/android/incallui/incall/impl/res/values/styles.xml
+++ b/java/com/android/incallui/incall/impl/res/values/styles.xml
@@ -17,6 +17,9 @@
<resources>
+ <color name="paginator_dot">#FFF</color>
+ <color name="paginator_path">#66FFFFFF</color>
+
<style name="DialpadContainer">
<item name="android:layout_alignParentTop">true</item>
</style>
diff --git a/java/com/android/incallui/maps/MapsComponent.java b/java/com/android/incallui/maps/MapsComponent.java
index 1ca17b781..796abaa92 100644
--- a/java/com/android/incallui/maps/MapsComponent.java
+++ b/java/com/android/incallui/maps/MapsComponent.java
@@ -19,29 +19,18 @@ package com.android.incallui.maps;
import android.content.Context;
import com.android.dialer.inject.HasRootComponent;
import dagger.Subcomponent;
-import com.android.incallui.maps.stub.StubMapsModule;
/** Subcomponent that can be used to access the maps implementation. */
-public class MapsComponent {
+@Subcomponent
+public abstract class MapsComponent {
- private static MapsComponent instance;
- private Maps maps;
-
- public Maps getMaps() {
- if (maps == null) {
- maps = new StubMapsModule.StubMaps();
- }
- return maps;
- }
+ public abstract Maps getMaps();
public static MapsComponent get(Context context) {
- if (instance == null) {
- instance = new MapsComponent();
- }
- return instance;
+ return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
+ .mapsComponent();
}
-
/** Used to refer to the root application component. */
public interface HasComponent {
MapsComponent mapsComponent();
diff --git a/java/com/android/incallui/maps/stub/StubMapsModule.java b/java/com/android/incallui/maps/stub/StubMapsModule.java
index 72678143c..3a193b15d 100644
--- a/java/com/android/incallui/maps/stub/StubMapsModule.java
+++ b/java/com/android/incallui/maps/stub/StubMapsModule.java
@@ -34,7 +34,7 @@ public abstract class StubMapsModule {
@Singleton
public abstract Maps bindMaps(StubMaps maps);
- static public final class StubMaps implements Maps {
+ static final class StubMaps implements Maps {
@Inject
public StubMaps() {}
diff --git a/java/com/android/incallui/res/values-uz/strings.xml b/java/com/android/incallui/res/values-uz/strings.xml
index fcf221f38..585f983ac 100644
--- a/java/com/android/incallui/res/values-uz/strings.xml
+++ b/java/com/android/incallui/res/values-uz/strings.xml
@@ -30,7 +30,7 @@
<string name="caller_manage_header" msgid="7358710345135355578">"Konferensiya qo‘ng‘irog‘i <xliff:g id="CONF_CALL_TIME">%s</xliff:g>"</string>
<string name="voicemail_settings_number_label" msgid="8935904934161608885">"Ovozli pochta raqami"</string>
<string name="notification_dialing" msgid="9072177265772083826">"Raqam terilmoqda"</string>
- <string name="notification_missedCallTicker" msgid="238492086972857643">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> qo‘ng‘irog‘i javobsiz qoldirildi"</string>
+ <string name="notification_missedCallTicker" msgid="238492086972857643">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> chaqiruvi javobsiz qoldi"</string>
<string name="notification_ongoing_call" msgid="8712641937577776125">"Joriy qo‘ng‘iroq"</string>
<string name="notification_ongoing_work_call" msgid="3189581218078981547">"Chiquvchi ishchi qo‘ng‘irog‘i"</string>
<string name="notification_ongoing_call_wifi" msgid="297183051021070949">"Chiquvchi Wi-Fi qo‘ng‘irog‘i"</string>
diff --git a/java/com/android/incallui/videotech/VideoTech.java b/java/com/android/incallui/videotech/VideoTech.java
index fb2641793..bd957b699 100644
--- a/java/com/android/incallui/videotech/VideoTech.java
+++ b/java/com/android/incallui/videotech/VideoTech.java
@@ -27,6 +27,12 @@ public interface VideoTech {
boolean isTransmittingOrReceiving();
+ /**
+ * Determines if the answer video UI should open the camera directly instead of letting the video
+ * tech manage the camera.
+ */
+ boolean isSelfManagedCamera();
+
void onCallStateChanged(int newState);
@SessionModificationState
diff --git a/java/com/android/incallui/videotech/empty/EmptyVideoTech.java b/java/com/android/incallui/videotech/empty/EmptyVideoTech.java
index bc8db4c07..c76043540 100644
--- a/java/com/android/incallui/videotech/empty/EmptyVideoTech.java
+++ b/java/com/android/incallui/videotech/empty/EmptyVideoTech.java
@@ -32,6 +32,11 @@ public class EmptyVideoTech implements VideoTech {
}
@Override
+ public boolean isSelfManagedCamera() {
+ return false;
+ }
+
+ @Override
public void onCallStateChanged(int newState) {}
@Override
diff --git a/java/com/android/incallui/videotech/ims/ImsVideoTech.java b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
index 890e5c80c..a37500c3b 100644
--- a/java/com/android/incallui/videotech/ims/ImsVideoTech.java
+++ b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
@@ -58,6 +58,13 @@ public class ImsVideoTech implements VideoTech {
}
@Override
+ public boolean isSelfManagedCamera() {
+ // Return false to indicate that the answer UI shouldn't open the camera itself.
+ // For IMS Video the modem is responsible for opening the camera.
+ return false;
+ }
+
+ @Override
public void onCallStateChanged(int newState) {
if (!isAvailable()) {
return;
diff --git a/java/com/android/incallui/videotech/rcs/RcsVideoShare.java b/java/com/android/incallui/videotech/rcs/RcsVideoShare.java
index 2cb43036f..1e951408c 100644
--- a/java/com/android/incallui/videotech/rcs/RcsVideoShare.java
+++ b/java/com/android/incallui/videotech/rcs/RcsVideoShare.java
@@ -65,6 +65,11 @@ public class RcsVideoShare implements VideoTech, CapabilitiesListener, VideoShar
}
@Override
+ public boolean isSelfManagedCamera() {
+ return true;
+ }
+
+ @Override
public void onCallStateChanged(int newState) {
if (newState == Call.STATE_DISCONNECTING) {
enrichedCallManager.unregisterVideoShareListener(this);