diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-03-07 02:20:21 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-03-07 02:20:21 +0000 |
commit | 28b252f6e0e4d42551a31d1b197f2eedf7b6a7d5 (patch) | |
tree | 454b1b7cfa72d077b125d9351a8a2fdd0272fd75 | |
parent | d554f435556b21e667d86b7ce9da67445a98ee5b (diff) | |
parent | da410d32f17dd823dba772181271ff55ee8e3eaf (diff) |
Merge "Show a warning dialog about charges when user starts a video call"
11 files changed, 324 insertions, 2 deletions
diff --git a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java index 236f77972..c4ed6e6ed 100644 --- a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java +++ b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java @@ -78,6 +78,13 @@ public class TelephonyManagerCompat { public static final Integer FEATURES_ASSISTED_DIALING = 1 << 4; /** + * Flag specifying whether to show an alert dialog for video call charges. By default this value + * is {@code false}. TODO(a bug): Replace with public API for these constants when available. + */ + public static final String CARRIER_CONFIG_KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = + "show_video_call_charges_alert_dialog_bool"; + + /** * Returns the number of phones available. Returns 1 for Single standby mode (Single SIM * functionality) Returns 2 for Dual standby mode.(Dual SIM functionality) * diff --git a/java/com/android/incallui/VideoCallPresenter.java b/java/com/android/incallui/VideoCallPresenter.java index a19d45f7a..f5d681c74 100644 --- a/java/com/android/incallui/VideoCallPresenter.java +++ b/java/com/android/incallui/VideoCallPresenter.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.content.Context; import android.graphics.Point; import android.os.Handler; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telecom.InCallService.VideoCall; import android.telecom.VideoProfile; @@ -80,7 +81,8 @@ public class VideoCallPresenter InCallDetailsListener, SurfaceChangeListener, InCallPresenter.InCallEventListener, - VideoCallScreenDelegate { + VideoCallScreenDelegate, + CallList.Listener { private static boolean isVideoMode = false; @@ -325,6 +327,8 @@ public class VideoCallPresenter InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate()); InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate()); + CallList.getInstance().addListener(this); + // Register for surface and video events from {@link InCallVideoCallListener}s. InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this); currentVideoState = VideoProfile.STATE_AUDIO_ONLY; @@ -354,6 +358,8 @@ public class VideoCallPresenter InCallPresenter.getInstance().removeInCallEventListener(this); InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null); + CallList.getInstance().removeListener(this); + InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this); // Ensure that the call's camera direction is updated (most likely to UNKNOWN). Normally this @@ -1126,6 +1132,34 @@ public class VideoCallPresenter || VideoUtils.hasReceivedVideoUpgradeRequest(state); } + @Override + public void onIncomingCall(DialerCall call) {} + + @Override + public void onUpgradeToVideo(DialerCall call) {} + + @Override + public void onSessionModificationStateChange(DialerCall call) {} + + @Override + public void onCallListChange(CallList callList) {} + + @Override + public void onDisconnect(DialerCall call) {} + + @Override + public void onWiFiToLteHandover(DialerCall call) { + if (call.isVideoCall() || call.hasSentVideoUpgradeRequest()) { + videoCallScreen.onHandoverFromWiFiToLte(); + } + } + + @Override + public void onHandoverToWifiFailed(DialerCall call) {} + + @Override + public void onInternationalCallOnWifi(@NonNull DialerCall call) {} + private class LocalDelegate implements VideoSurfaceDelegate { @Override public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) { diff --git a/java/com/android/incallui/answer/impl/AnswerVideoCallScreen.java b/java/com/android/incallui/answer/impl/AnswerVideoCallScreen.java index 2f10a5be9..7a21676bc 100644 --- a/java/com/android/incallui/answer/impl/AnswerVideoCallScreen.java +++ b/java/com/android/incallui/answer/impl/AnswerVideoCallScreen.java @@ -109,6 +109,9 @@ public class AnswerVideoCallScreen implements VideoCallScreen { return callId; } + @Override + public void onHandoverFromWiFiToLte() {} + private void updatePreviewVideoScaling() { if (textureView.getWidth() == 0 || textureView.getHeight() == 0) { LogUtil.i( diff --git a/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java b/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java index 522d77235..b74c1ec73 100644 --- a/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java +++ b/java/com/android/incallui/answer/impl/SelfManagedAnswerVideoCallScreen.java @@ -110,6 +110,9 @@ public class SelfManagedAnswerVideoCallScreen extends StateCallback implements V return callId; } + @Override + public void onHandoverFromWiFiToLte() {} + /** * Opens the first front facing camera on the device into a {@link SurfaceView} while preserving * aspect ratio. diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index d36b00d91..30d2bcb5e 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -17,6 +17,7 @@ package com.android.incallui.call; import android.Manifest.permission; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.hardware.camera2.CameraCharacteristics; @@ -25,6 +26,7 @@ import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import android.os.PersistableBundle; import android.os.Trace; import android.support.annotation.IntDef; import android.support.annotation.NonNull; @@ -156,6 +158,8 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa @Nullable private Boolean isInGlobalSpamList; private boolean didShowCameraPermission; + private boolean didDismissVideoChargesAlertDialog; + private PersistableBundle carrierConfig; private String callProviderLabel; private String callbackNumber; private int cameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN; @@ -464,7 +468,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa /* package-private */ Call getTelecomCall() { return telecomCall; } - + public StatusHints getStatusHints() { return telecomCall.getDetails().getStatusHints(); } @@ -585,6 +589,9 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa if (phoneAccount != null) { isCallSubjectSupported = phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); + if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { + cacheCarrierConfiguration(phoneAccountHandle); + } } } } @@ -597,6 +604,26 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } /** + * Caches frequently used carrier configuration locally. + * + * @param accountHandle The PhoneAccount handle. + */ + @SuppressLint("MissingPermission") + private void cacheCarrierConfiguration(PhoneAccountHandle accountHandle) { + if (!PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) { + return; + } + if (VERSION.SDK_INT < VERSION_CODES.O) { + return; + } + // TODO(a bug): This may take several seconds to complete, revisit it to move it to worker + // thread. + carrierConfig = + TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(context, accountHandle) + .getCarrierConfig(); + } + + /** * Tests corruption of the {@code callExtras} bundle by calling {@link * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will * be thrown and caught by this function. @@ -712,6 +739,14 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa doNotShowDialogForHandoffToWifiFailure = bool; } + public boolean showVideoChargesAlertDialog() { + if (carrierConfig == null) { + return false; + } + return carrierConfig.getBoolean( + TelephonyManagerCompat.CARRIER_CONFIG_KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL); + } + public long getTimeAddedMs() { return timeAddedMs; } @@ -1071,6 +1106,14 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa didShowCameraPermission = didShow; } + public boolean didDismissVideoChargesAlertDialog() { + return didDismissVideoChargesAlertDialog; + } + + public void setDidDismissVideoChargesAlertDialog(boolean didDismiss) { + didDismissVideoChargesAlertDialog = didDismiss; + } + @Nullable public Boolean isInGlobalSpamList() { return isInGlobalSpamList; diff --git a/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java b/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java index 28ee774ba..b97d2eb16 100644 --- a/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java +++ b/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java @@ -725,6 +725,9 @@ public class SurfaceViewVideoCallFragment extends Fragment } @Override + public void onHandoverFromWiFiToLte() {} + + @Override public void showButton(@InCallButtonIds int buttonId, boolean show) { LogUtil.v( "SurfaceViewVideoCallFragment.showButton", diff --git a/java/com/android/incallui/video/impl/VideoCallFragment.java b/java/com/android/incallui/video/impl/VideoCallFragment.java index 6b5a9797f..2a810cfcd 100644 --- a/java/com/android/incallui/video/impl/VideoCallFragment.java +++ b/java/com/android/incallui/video/impl/VideoCallFragment.java @@ -99,6 +99,8 @@ public class VideoCallFragment extends Fragment @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) static final String ARG_CALL_ID = "call_id"; + private static final String TAG_VIDEO_CHARGES_ALERT = "tag_video_charges_alert"; + @VisibleForTesting static final float BLUR_PREVIEW_RADIUS = 16.0f; @VisibleForTesting static final float BLUR_PREVIEW_SCALE_FACTOR = 1.0f; private static final float BLUR_REMOTE_RADIUS = 25.0f; @@ -108,6 +110,7 @@ public class VideoCallFragment extends Fragment private static final int CAMERA_PERMISSION_REQUEST_CODE = 1; 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 static final long VIDEO_CHARGES_ALERT_DIALOG_DELAY_IN_MILLIS = 500L; private final ViewOutlineProvider circleOutlineProvider = new ViewOutlineProvider() { @@ -162,6 +165,24 @@ public class VideoCallFragment extends Fragment } }; + private final Runnable videoChargesAlertDialogRunnable = + () -> { + VideoChargesAlertDialogFragment existingVideoChargesAlertFragment = + (VideoChargesAlertDialogFragment) + getChildFragmentManager().findFragmentByTag(TAG_VIDEO_CHARGES_ALERT); + if (existingVideoChargesAlertFragment != null) { + LogUtil.i( + "VideoCallFragment.videoChargesAlertDialogRunnable", "already shown for this call"); + return; + } + + if (VideoChargesAlertDialogFragment.shouldShow(getContext(), getCallId())) { + LogUtil.i("VideoCallFragment.videoChargesAlertDialogRunnable", "showing dialog"); + VideoChargesAlertDialogFragment.newInstance(getCallId()) + .show(getChildFragmentManager(), TAG_VIDEO_CHARGES_ALERT); + } + }; + public static VideoCallFragment newInstance(String callId) { Bundle bundle = new Bundle(); bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId)); @@ -352,6 +373,8 @@ public class VideoCallFragment extends Fragment inCallButtonUiDelegate.refreshMuteState(); videoCallScreenDelegate.onVideoCallScreenUiReady(); getView().postDelayed(cameraPermissionDialogRunnable, CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS); + getView() + .postDelayed(videoChargesAlertDialogRunnable, VIDEO_CHARGES_ALERT_DIALOG_DELAY_IN_MILLIS); } @Override @@ -377,6 +400,7 @@ public class VideoCallFragment extends Fragment @Override public void onVideoScreenStop() { + getView().removeCallbacks(videoChargesAlertDialogRunnable); getView().removeCallbacks(cameraPermissionDialogRunnable); videoCallScreenDelegate.onVideoCallScreenUiUnready(); } @@ -768,6 +792,11 @@ public class VideoCallFragment extends Fragment } @Override + public void onHandoverFromWiFiToLte() { + getView().post(videoChargesAlertDialogRunnable); + } + + @Override public void showButton(@InCallButtonIds int buttonId, boolean show) { LogUtil.v( "VideoCallFragment.showButton", diff --git a/java/com/android/incallui/video/impl/VideoChargesAlertDialogFragment.java b/java/com/android/incallui/video/impl/VideoChargesAlertDialogFragment.java new file mode 100644 index 000000000..6762a9d22 --- /dev/null +++ b/java/com/android/incallui/video/impl/VideoChargesAlertDialogFragment.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 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.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.support.v4.app.DialogFragment; +import android.support.v4.os.UserManagerCompat; +import android.telecom.Call.Details; +import android.view.View; +import android.widget.CheckBox; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; + +/** Alert dialog for video charges. */ +public class VideoChargesAlertDialogFragment extends DialogFragment { + + /** Preference key for whether to show the alert dialog for video charges next time. */ + @VisibleForTesting + static final String KEY_DO_NOT_SHOW_VIDEO_CHARGES_ALERT = "key_do_not_show_video_charges_alert"; + + /** Key in the arguments bundle for call id. */ + private static final String ARG_CALL_ID = "call_id"; + + /** + * Returns {@code true} if an {@link VideoChargesAlertDialogFragment} should be shown. + * + * <p>Attempting to show an VideoChargesAlertDialogFragment when this method returns {@code false} + * will result in an {@link IllegalStateException}. + */ + public static boolean shouldShow(@NonNull Context context, String callId) { + DialerCall call = CallList.getInstance().getCallById(callId); + if (call == null) { + LogUtil.i("VideoChargesAlertDialogFragment.shouldShow", "null call"); + return false; + } + + if (call.hasProperty(Details.PROPERTY_WIFI)) { + return false; + } + + if (call.didDismissVideoChargesAlertDialog()) { + LogUtil.i( + "VideoChargesAlertDialogFragment.shouldShow", "The dialog has been dismissed by user"); + return false; + } + + if (!call.showVideoChargesAlertDialog()) { + return false; + } + + if (!UserManagerCompat.isUserUnlocked(context)) { + LogUtil.i("VideoChargesAlertDialogFragment.shouldShow", "user locked, returning false"); + return false; + } + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + if (preferences.getBoolean(KEY_DO_NOT_SHOW_VIDEO_CHARGES_ALERT, false)) { + LogUtil.i( + "VideoChargesAlertDialogFragment.shouldShow", + "Video charges alert has been disabled by user, returning false"); + return false; + } + + return true; + } + + /** + * Returns a new instance of {@link VideoChargesAlertDialogFragment} + * + * <p>Prefer this method over the default constructor. + */ + public static VideoChargesAlertDialogFragment newInstance(@NonNull String callId) { + VideoChargesAlertDialogFragment fragment = new VideoChargesAlertDialogFragment(); + Bundle args = new Bundle(); + args.putString(ARG_CALL_ID, Assert.isNotNull(callId)); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle bundle) { + super.onCreateDialog(bundle); + + if (!VideoChargesAlertDialogFragment.shouldShow( + getActivity(), getArguments().getString(ARG_CALL_ID))) { + throw new IllegalStateException( + "shouldShow indicated VideoChargesAlertDialogFragment should not have showed"); + } + + View dialogView = View.inflate(getActivity(), R.layout.frag_video_charges_alert_dialog, null); + + CheckBox alertCheckBox = dialogView.findViewById(R.id.do_not_show); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + AlertDialog alertDialog = + new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme) + .setView(dialogView) + .setPositiveButton( + android.R.string.ok, + (dialog, which) -> onPositiveButtonClicked(preferences, alertCheckBox.isChecked())) + .create(); + this.setCancelable(false); + return alertDialog; + } + + private void onPositiveButtonClicked(@NonNull SharedPreferences preferences, boolean isChecked) { + LogUtil.i( + "VideoChargesAlertDialogFragment.onPositiveButtonClicked", "isChecked: %b", isChecked); + preferences.edit().putBoolean(KEY_DO_NOT_SHOW_VIDEO_CHARGES_ALERT, isChecked).apply(); + + DialerCall dialerCall = + CallList.getInstance().getCallById(getArguments().getString(ARG_CALL_ID)); + if (dialerCall != null) { + dialerCall.setDidDismissVideoChargesAlertDialog(true); + } + } +} diff --git a/java/com/android/incallui/video/impl/res/layout/frag_video_charges_alert_dialog.xml b/java/com/android/incallui/video/impl/res/layout/frag_video_charges_alert_dialog.xml new file mode 100644 index 000000000..a547c7d69 --- /dev/null +++ b/java/com/android/incallui/video/impl/res/layout/frag_video_charges_alert_dialog.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="24dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" + android:paddingBottom="4dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:text="@string/videocall_charges_alert_dialog_description" + android:textColor="@color/dialer_primary_text_color" + android:textSize="16sp"/> + + <CheckBox + android:id="@+id/do_not_show" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:buttonTint="@color/dialer_theme_color" + android:focusable="true" + android:clickable="true" + android:text="@string/do_not_show_again" + android:textColor="@color/dialer_primary_text_color" + android:textSize="14sp"/> + </LinearLayout> +</ScrollView>
\ No newline at end of file diff --git a/java/com/android/incallui/video/impl/res/values/strings.xml b/java/com/android/incallui/video/impl/res/values/strings.xml index 58ea8bde7..6c90af5f3 100644 --- a/java/com/android/incallui/video/impl/res/values/strings.xml +++ b/java/com/android/incallui/video/impl/res/values/strings.xml @@ -29,4 +29,10 @@ <!-- Text indicates the call is resumed from held by remote party. [CHAR LIMIT=20] --> <string name="videocall_remotely_resumed">Call resumed</string> + <!-- Instruction text to notify user that charges may apply on video calling. [CHAR LIMIT=NONE] --> + <string name="videocall_charges_alert_dialog_description">Video calls made over the mobile network use both data and voice minutes. Charges may apply.</string> + + <!-- Option to hide the popup dialog if it is not necessary for the user. [CHAR LIMIT=40] --> + <string name="do_not_show_again">Do not show again</string> + </resources> diff --git a/java/com/android/incallui/video/protocol/VideoCallScreen.java b/java/com/android/incallui/video/protocol/VideoCallScreen.java index bad050cd1..582d4c64b 100644 --- a/java/com/android/incallui/video/protocol/VideoCallScreen.java +++ b/java/com/android/incallui/video/protocol/VideoCallScreen.java @@ -39,4 +39,6 @@ public interface VideoCallScreen { Fragment getVideoCallScreenFragment(); String getCallId(); + + void onHandoverFromWiFiToLte(); } |