From 06b6b56e9eaa91ebf757ea641e38a9c885fa40bd Mon Sep 17 00:00:00 2001 From: Eric Erfanian Date: Mon, 20 Mar 2017 08:50:25 -0700 Subject: Update AOSP Dialer source from internal google3 repository at cl/150622237 Test: make, treehugger, on device testing. This CL updates the AOSP Dialer source with all the changes that have gone into the private google3 repository. This includes all the changes from cl/150392808 (3/16/2017) to cl/150622237 (3/20/2017). This goal of these drops is to keep the AOSP source in sync with the internal google3 repository. Currently these sync are done by hand with very minor modifications to the internal source code. See the Android.mk file for list of modifications. Our current goal is to do frequent drops (daily if possible) and eventually switched to an automated process. Change-Id: Id53e0e580a4ef73760a8afb7bb8c265ee27ad535 --- java/com/android/incallui/InCallActivity.java | 6 +- java/com/android/incallui/InCallPresenter.java | 2 +- .../incallui/answer/bindings/AnswerBindings.java | 8 +- .../incallui/answer/impl/AnswerFragment.java | 19 +- .../answer/impl/FixedAspectSurfaceView.java | 86 +++++++ .../impl/SelfManagedAnswerVideoCallScreen.java | 268 +++++++++++++++++++++ .../impl/res/layout/fragment_incoming_call.xml | 20 +- .../answer/impl/res/values-land/dimens.xml | 20 ++ .../incallui/answer/impl/res/values/attrs.xml | 26 ++ .../incallui/answer/impl/res/values/dimens.xml | 3 +- .../calllocation/CallLocationComponent.java | 20 +- .../incallui/calllocation/impl/HttpFetcher.java | 2 + .../calllocation/stub/StubCallLocationModule.java | 6 +- .../incallui/incall/impl/AndroidManifest.xml | 4 +- .../incallui/incall/impl/InCallFragment.java | 29 ++- .../incallui/incall/impl/InCallPaginator.java | 210 ++++++++++++++++ .../incallui/incall/impl/LockableViewPager.java | 46 ++++ .../incall/impl/res/layout/frag_incall_voice.xml | 20 +- .../incallui/incall/impl/res/values/dimens.xml | 5 + .../incallui/incall/impl/res/values/styles.xml | 3 + java/com/android/incallui/maps/MapsComponent.java | 21 +- .../android/incallui/maps/stub/StubMapsModule.java | 2 +- .../com/android/incallui/res/values-uz/strings.xml | 2 +- java/com/android/incallui/videotech/VideoTech.java | 6 + .../incallui/videotech/empty/EmptyVideoTech.java | 5 + .../incallui/videotech/ims/ImsVideoTech.java | 7 + .../incallui/videotech/rcs/RcsVideoShare.java | 5 + 27 files changed, 777 insertions(+), 74 deletions(-) create mode 100644 java/com/android/incallui/answer/impl/FixedAspectSurfaceView.java create mode 100644 java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java create mode 100644 java/com/android/incallui/answer/impl/res/values-land/dimens.xml create mode 100644 java/com/android/incallui/answer/impl/res/values/attrs.xml create mode 100644 java/com/android/incallui/incall/impl/InCallPaginator.java create mode 100644 java/com/android/incallui/incall/impl/LockableViewPager.java (limited to 'java/com/android/incallui') 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. + * + *

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 --> - + android:id="@+id/incoming_preview_texture_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:importantForAccessibility="no" + android:visibility="gone"/> + + + + + false + true + 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 @@ + + + + + + + + + + -1 + -2 + \ 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 --> - 24sp 24sp @@ -22,5 +21,7 @@ 0dp 0dp false + true + false 1000 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. * + *

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 @@ - - + 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 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"/> - - + false 0 + + 5dp + 8dp + 38dp + 72dp 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 @@ + #FFF + #66FFFFFF + 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 @@ "Konferensiya qo‘ng‘irog‘i %s" "Ovozli pochta raqami" "Raqam terilmoqda" - "%s qo‘ng‘irog‘i javobsiz qoldirildi" + "%s chaqiruvi javobsiz qoldi" "Joriy qo‘ng‘iroq" "Chiquvchi ishchi qo‘ng‘irog‘i" "Chiquvchi Wi-Fi qo‘ng‘irog‘i" 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 @@ -31,6 +31,11 @@ public class EmptyVideoTech implements VideoTech { return false; } + @Override + public boolean isSelfManagedCamera() { + return false; + } + @Override public void onCallStateChanged(int newState) {} 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 @@ -57,6 +57,13 @@ public class ImsVideoTech implements VideoTech { return VideoProfile.isVideo(call.getDetails().getVideoState()); } + @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()) { 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 @@ -64,6 +64,11 @@ public class RcsVideoShare implements VideoTech, CapabilitiesListener, VideoShar || receivingSessionId != Session.NO_SESSION_ID; } + @Override + public boolean isSelfManagedCamera() { + return true; + } + @Override public void onCallStateChanged(int newState) { if (newState == Call.STATE_DISCONNECTING) { -- cgit v1.2.3