summaryrefslogtreecommitdiff
path: root/java/com/android/incallui/answer/impl/hint
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/incallui/answer/impl/hint')
-rw-r--r--java/com/android/incallui/answer/impl/hint/AndroidManifest.xml13
-rw-r--r--java/com/android/incallui/answer/impl/hint/AnswerHint.java46
-rw-r--r--java/com/android/incallui/answer/impl/hint/AnswerHintFactory.java133
-rw-r--r--java/com/android/incallui/answer/impl/hint/DotAnswerHint.java283
-rw-r--r--java/com/android/incallui/answer/impl/hint/EmptyAnswerHint.java39
-rw-r--r--java/com/android/incallui/answer/impl/hint/EventAnswerHint.java235
-rw-r--r--java/com/android/incallui/answer/impl/hint/EventPayloadLoader.java30
-rw-r--r--java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java118
-rw-r--r--java/com/android/incallui/answer/impl/hint/EventSecretCodeListener.java67
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_large.xml4
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_mid.xml4
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_small.xml5
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/layout/dot_hint.xml30
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/layout/event_hint.xml36
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/values/dimens.xml12
-rw-r--r--java/com/android/incallui/answer/impl/hint/res/values/strings.xml5
16 files changed, 1060 insertions, 0 deletions
diff --git a/java/com/android/incallui/answer/impl/hint/AndroidManifest.xml b/java/com/android/incallui/answer/impl/hint/AndroidManifest.xml
new file mode 100644
index 000000000..b5fa6da8f
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest
+ package="com.android.incallui.answer.impl.hint"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application>
+ <receiver android:name=".EventSecretCodeListener">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SECRET_CODE" />
+ <data android:scheme="android_secret_code" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/java/com/android/incallui/answer/impl/hint/AnswerHint.java b/java/com/android/incallui/answer/impl/hint/AnswerHint.java
new file mode 100644
index 000000000..dd3b8228a
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/AnswerHint.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.answer.impl.hint;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/** Interface to overlay a hint of how to answer the call. */
+public interface AnswerHint {
+
+ /**
+ * Inflates the hint's layout into the container.
+ *
+ * <p>TODO: if the hint becomes more dependent on other UI elements of the AnswerFragment,
+ * should put put and hintText into another data structure.
+ */
+ void onCreateView(LayoutInflater inflater, ViewGroup container, View puck, TextView hintText);
+
+ /** Called when the puck bounce animation begins. */
+ void onBounceStart();
+
+ /**
+ * Called when the bounce animation has ended (transitioned into other animations). The hint
+ * should reset itself.
+ */
+ void onBounceEnd();
+
+ /** Called when the call is accepted or rejected through user interaction. */
+ void onAnswered();
+}
diff --git a/java/com/android/incallui/answer/impl/hint/AnswerHintFactory.java b/java/com/android/incallui/answer/impl/hint/AnswerHintFactory.java
new file mode 100644
index 000000000..45395a71f
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/AnswerHintFactory.java
@@ -0,0 +1,133 @@
+/*
+ * 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.answer.impl.hint;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.ConfigProvider;
+import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.common.LogUtil;
+import com.android.incallui.util.AccessibilityUtil;
+import java.util.Calendar;
+
+/**
+ * Selects a AnswerHint to show. If there's no suitable hints {@link EmptyAnswerHint} will be used,
+ * which does nothing.
+ */
+public class AnswerHintFactory {
+
+ private static final String CONFIG_ANSWER_HINT_ANSWERED_THRESHOLD_KEY =
+ "answer_hint_answered_threshold";
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String CONFIG_ANSWER_HINT_WHITELISTED_DEVICES_KEY =
+ "answer_hint_whitelisted_devices";
+ // Most popular devices released before NDR1 is whitelisted. Their user are likely to have seen
+ // the legacy UI.
+ private static final String DEFAULT_WHITELISTED_DEVICES_CSV =
+ "/hammerhead//bullhead//angler//shamu//gm4g//gm4g_s//AQ4501//gce_x86_phone//gm4gtkc_s/"
+ + "/Sparkle_V//Mi-498//AQ4502//imobileiq2//A65//H940//m8_google//m0xx//A10//ctih220/"
+ + "/Mi438S//bacon/";
+
+ @VisibleForTesting
+ static final String ANSWERED_COUNT_PREFERENCE_KEY = "answer_hint_answered_count";
+
+ private final EventPayloadLoader eventPayloadLoader;
+
+ public AnswerHintFactory(@NonNull EventPayloadLoader eventPayloadLoader) {
+ this.eventPayloadLoader = Assert.isNotNull(eventPayloadLoader);
+ }
+
+ @NonNull
+ public AnswerHint create(Context context, long puckUpDuration, long puckUpDelay) {
+
+ if (shouldShowAnswerHint(
+ context,
+ ConfigProviderBindings.get(context),
+ getDeviceProtectedPreferences(context),
+ Build.PRODUCT)) {
+ return new DotAnswerHint(context, puckUpDuration, puckUpDelay);
+ }
+
+ // Display the event answer hint if the payload is available.
+ Drawable eventPayload =
+ eventPayloadLoader.loadPayload(
+ context, System.currentTimeMillis(), Calendar.getInstance().getTimeZone());
+ if (eventPayload != null) {
+ return new EventAnswerHint(context, eventPayload, puckUpDuration, puckUpDelay);
+ }
+
+ return new EmptyAnswerHint();
+ }
+
+ public static void increaseAnsweredCount(Context context) {
+ SharedPreferences sharedPreferences = getDeviceProtectedPreferences(context);
+ int answeredCount = sharedPreferences.getInt(ANSWERED_COUNT_PREFERENCE_KEY, 0);
+ sharedPreferences.edit().putInt(ANSWERED_COUNT_PREFERENCE_KEY, answeredCount + 1).apply();
+ }
+
+ @VisibleForTesting
+ static boolean shouldShowAnswerHint(
+ Context context,
+ ConfigProvider configProvider,
+ SharedPreferences sharedPreferences,
+ String device) {
+ if (AccessibilityUtil.isTouchExplorationEnabled(context)) {
+ return false;
+ }
+ // Devices that has the legacy dialer installed are whitelisted as they are likely to go through
+ // a UX change during updates.
+ if (!isDeviceWhitelisted(device, configProvider)) {
+ return false;
+ }
+
+ // If the user has gone through the process a few times we can assume they have learnt the
+ // method.
+ int answeredCount = sharedPreferences.getInt(ANSWERED_COUNT_PREFERENCE_KEY, 0);
+ long threshold = configProvider.getLong(CONFIG_ANSWER_HINT_ANSWERED_THRESHOLD_KEY, 3);
+ LogUtil.i(
+ "AnswerHintFactory.shouldShowAnswerHint",
+ "answerCount: %d, threshold: %d",
+ answeredCount,
+ threshold);
+ return answeredCount < threshold;
+ }
+
+ /**
+ * @param device should be the value of{@link Build#PRODUCT}.
+ * @param configProvider should provide a list of devices quoted with '/' concatenated to a
+ * string.
+ */
+ private static boolean isDeviceWhitelisted(String device, ConfigProvider configProvider) {
+ return configProvider
+ .getString(CONFIG_ANSWER_HINT_WHITELISTED_DEVICES_KEY, DEFAULT_WHITELISTED_DEVICES_CSV)
+ .contains("/" + device + "/");
+ }
+
+ private static SharedPreferences getDeviceProtectedPreferences(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+ return PreferenceManager.getDefaultSharedPreferences(
+ context.createDeviceProtectedStorageContext());
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/hint/DotAnswerHint.java b/java/com/android/incallui/answer/impl/hint/DotAnswerHint.java
new file mode 100644
index 000000000..394fe5808
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/DotAnswerHint.java
@@ -0,0 +1,283 @@
+/*
+ * 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.answer.impl.hint;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.support.annotation.DimenRes;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.TextView;
+
+/** An Answer hint that uses a green swiping dot. */
+public class DotAnswerHint implements AnswerHint {
+
+ private static final float ANSWER_HINT_SMALL_ALPHA = 0.8f;
+ private static final float ANSWER_HINT_MID_ALPHA = 0.5f;
+ private static final float ANSWER_HINT_LARGE_ALPHA = 0.2f;
+
+ private static final long FADE_IN_DELAY_SCALE_MILLIS = 380;
+ private static final long FADE_IN_DURATION_SCALE_MILLIS = 200;
+ private static final long FADE_IN_DELAY_ALPHA_MILLIS = 340;
+ private static final long FADE_IN_DURATION_ALPHA_MILLIS = 50;
+
+ private static final long SWIPE_UP_DURATION_ALPHA_MILLIS = 500;
+
+ private static final long FADE_OUT_DELAY_SCALE_SMALL_MILLIS = 90;
+ private static final long FADE_OUT_DELAY_SCALE_MID_MILLIS = 70;
+ private static final long FADE_OUT_DELAY_SCALE_LARGE_MILLIS = 10;
+ private static final long FADE_OUT_DURATION_SCALE_MILLIS = 100;
+ private static final long FADE_OUT_DELAY_ALPHA_MILLIS = 130;
+ private static final long FADE_OUT_DURATION_ALPHA_MILLIS = 170;
+
+ private final Context context;
+ private final long puckUpDurationMillis;
+ private final long puckUpDelayMillis;
+
+ private View puck;
+
+ private View answerHintSmall;
+ private View answerHintMid;
+ private View answerHintLarge;
+ private View answerHintContainer;
+ private AnimatorSet answerGestureHintAnim;
+
+ public DotAnswerHint(Context context, long puckUpDurationMillis, long puckUpDelayMillis) {
+ this.context = context;
+ this.puckUpDurationMillis = puckUpDurationMillis;
+ this.puckUpDelayMillis = puckUpDelayMillis;
+ }
+
+ @Override
+ public void onCreateView(
+ LayoutInflater inflater, ViewGroup container, View puck, TextView hintText) {
+ this.puck = puck;
+ View view = inflater.inflate(R.layout.dot_hint, container, true);
+ answerHintContainer = view.findViewById(R.id.answer_hint_container);
+ answerHintSmall = view.findViewById(R.id.answer_hint_small);
+ answerHintMid = view.findViewById(R.id.answer_hint_mid);
+ answerHintLarge = view.findViewById(R.id.answer_hint_large);
+ hintText.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.hint_text_size));
+ }
+
+ @Override
+ public void onBounceStart() {
+ if (answerGestureHintAnim == null) {
+ answerGestureHintAnim = new AnimatorSet();
+ answerHintContainer.setY(puck.getY() + getDimension(R.dimen.hint_initial_offset));
+
+ Animator fadeIn = createFadeIn();
+
+ Animator swipeUp =
+ ObjectAnimator.ofFloat(
+ answerHintContainer,
+ View.TRANSLATION_Y,
+ puck.getY() - getDimension(R.dimen.hint_offset));
+ swipeUp.setInterpolator(new FastOutSlowInInterpolator());
+ swipeUp.setDuration(SWIPE_UP_DURATION_ALPHA_MILLIS);
+
+ Animator fadeOut = createFadeOut();
+
+ answerGestureHintAnim.play(fadeIn).after(puckUpDelayMillis);
+ answerGestureHintAnim.play(swipeUp).after(fadeIn);
+ // The fade out should start fading the alpha just as the puck is dropping. Scaling will start
+ // a bit earlier.
+ answerGestureHintAnim
+ .play(fadeOut)
+ .after(puckUpDelayMillis + puckUpDurationMillis - FADE_OUT_DELAY_ALPHA_MILLIS);
+
+ fadeIn.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ answerHintSmall.setAlpha(0);
+ answerHintSmall.setScaleX(1);
+ answerHintSmall.setScaleY(1);
+ answerHintMid.setAlpha(0);
+ answerHintMid.setScaleX(1);
+ answerHintMid.setScaleY(1);
+ answerHintLarge.setAlpha(0);
+ answerHintLarge.setScaleX(1);
+ answerHintLarge.setScaleY(1);
+ answerHintContainer.setY(puck.getY() + getDimension(R.dimen.hint_initial_offset));
+ answerHintContainer.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ answerGestureHintAnim.start();
+ }
+
+ private Animator createFadeIn() {
+ AnimatorSet set = new AnimatorSet();
+ set.play(
+ createFadeInScaleAndAlpha(
+ answerHintSmall,
+ R.dimen.hint_small_begin_size,
+ R.dimen.hint_small_end_size,
+ ANSWER_HINT_SMALL_ALPHA))
+ .with(
+ createFadeInScaleAndAlpha(
+ answerHintMid,
+ R.dimen.hint_mid_begin_size,
+ R.dimen.hint_mid_end_size,
+ ANSWER_HINT_MID_ALPHA))
+ .with(
+ createFadeInScaleAndAlpha(
+ answerHintLarge,
+ R.dimen.hint_large_begin_size,
+ R.dimen.hint_large_end_size,
+ ANSWER_HINT_LARGE_ALPHA));
+ return set;
+ }
+
+ private Animator createFadeInScaleAndAlpha(
+ View target, @DimenRes int beginSize, @DimenRes int endSize, float endAlpha) {
+ Animator scale =
+ createUniformScaleAnimator(
+ target,
+ getDimension(beginSize),
+ getDimension(beginSize),
+ getDimension(endSize),
+ FADE_IN_DURATION_SCALE_MILLIS,
+ FADE_IN_DELAY_SCALE_MILLIS,
+ new LinearInterpolator());
+ Animator alpha =
+ createAlphaAnimator(
+ target,
+ 0f,
+ endAlpha,
+ FADE_IN_DURATION_ALPHA_MILLIS,
+ FADE_IN_DELAY_ALPHA_MILLIS,
+ new LinearInterpolator());
+ AnimatorSet set = new AnimatorSet();
+ set.play(scale).with(alpha);
+ return set;
+ }
+
+ private Animator createFadeOut() {
+ AnimatorSet set = new AnimatorSet();
+ set.play(
+ createFadeOutScaleAndAlpha(
+ answerHintSmall,
+ R.dimen.hint_small_begin_size,
+ R.dimen.hint_small_end_size,
+ FADE_OUT_DELAY_SCALE_SMALL_MILLIS,
+ ANSWER_HINT_SMALL_ALPHA))
+ .with(
+ createFadeOutScaleAndAlpha(
+ answerHintMid,
+ R.dimen.hint_mid_begin_size,
+ R.dimen.hint_mid_end_size,
+ FADE_OUT_DELAY_SCALE_MID_MILLIS,
+ ANSWER_HINT_MID_ALPHA))
+ .with(
+ createFadeOutScaleAndAlpha(
+ answerHintLarge,
+ R.dimen.hint_large_begin_size,
+ R.dimen.hint_large_end_size,
+ FADE_OUT_DELAY_SCALE_LARGE_MILLIS,
+ ANSWER_HINT_LARGE_ALPHA));
+ return set;
+ }
+
+ private Animator createFadeOutScaleAndAlpha(
+ View target,
+ @DimenRes int beginSize,
+ @DimenRes int endSize,
+ long scaleDelay,
+ float endAlpha) {
+ Animator scale =
+ createUniformScaleAnimator(
+ target,
+ getDimension(beginSize),
+ getDimension(endSize),
+ getDimension(beginSize),
+ FADE_OUT_DURATION_SCALE_MILLIS,
+ scaleDelay,
+ new LinearInterpolator());
+ Animator alpha =
+ createAlphaAnimator(
+ target,
+ endAlpha,
+ 0.0f,
+ FADE_OUT_DURATION_ALPHA_MILLIS,
+ FADE_OUT_DELAY_ALPHA_MILLIS,
+ new LinearInterpolator());
+ AnimatorSet set = new AnimatorSet();
+ set.play(scale).with(alpha);
+ return set;
+ }
+
+ @Override
+ public void onBounceEnd() {
+ if (answerGestureHintAnim != null) {
+ answerGestureHintAnim.end();
+ answerGestureHintAnim = null;
+ answerHintContainer.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onAnswered() {
+ AnswerHintFactory.increaseAnsweredCount(context);
+ }
+
+ private float getDimension(@DimenRes int id) {
+ return context.getResources().getDimension(id);
+ }
+
+ private static Animator createUniformScaleAnimator(
+ View target,
+ float original,
+ float begin,
+ float end,
+ long duration,
+ long delay,
+ Interpolator interpolator) {
+ float scaleBegin = begin / original;
+ float scaleEnd = end / original;
+ Animator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, scaleBegin, scaleEnd);
+ Animator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, scaleBegin, scaleEnd);
+ scaleX.setDuration(duration);
+ scaleY.setDuration(duration);
+ scaleX.setInterpolator(interpolator);
+ scaleY.setInterpolator(interpolator);
+ AnimatorSet set = new AnimatorSet();
+ set.play(scaleX).with(scaleY).after(delay);
+ return set;
+ }
+
+ private static Animator createAlphaAnimator(
+ View target, float begin, float end, long duration, long delay, Interpolator interpolator) {
+ Animator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, begin, end);
+ alpha.setDuration(duration);
+ alpha.setInterpolator(interpolator);
+ alpha.setStartDelay(delay);
+ return alpha;
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/hint/EmptyAnswerHint.java b/java/com/android/incallui/answer/impl/hint/EmptyAnswerHint.java
new file mode 100644
index 000000000..e52b4ee36
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/EmptyAnswerHint.java
@@ -0,0 +1,39 @@
+/*
+ * 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.answer.impl.hint;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/** Does nothing. Used to avoid null checks on AnswerHint. */
+public class EmptyAnswerHint implements AnswerHint {
+
+ @Override
+ public void onCreateView(
+ LayoutInflater inflater, ViewGroup container, View puck, TextView hintText) {}
+
+ @Override
+ public void onBounceStart() {}
+
+ @Override
+ public void onBounceEnd() {}
+
+ @Override
+ public void onAnswered() {}
+}
diff --git a/java/com/android/incallui/answer/impl/hint/EventAnswerHint.java b/java/com/android/incallui/answer/impl/hint/EventAnswerHint.java
new file mode 100644
index 000000000..7ee327d50
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/EventAnswerHint.java
@@ -0,0 +1,235 @@
+/*
+ * 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.answer.impl.hint;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DimenRes;
+import android.support.annotation.NonNull;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.dialer.common.Assert;
+
+/**
+ * An Answer hint that animates a {@link Drawable} payload with animation similar to {@link
+ * DotAnswerHint}.
+ */
+public final class EventAnswerHint implements AnswerHint {
+
+ private static final long FADE_IN_DELAY_SCALE_MILLIS = 380;
+ private static final long FADE_IN_DURATION_SCALE_MILLIS = 200;
+ private static final long FADE_IN_DELAY_ALPHA_MILLIS = 340;
+ private static final long FADE_IN_DURATION_ALPHA_MILLIS = 50;
+
+ private static final long SWIPE_UP_DURATION_ALPHA_MILLIS = 500;
+
+ private static final long FADE_OUT_DELAY_SCALE_SMALL_MILLIS = 90;
+ private static final long FADE_OUT_DURATION_SCALE_MILLIS = 100;
+ private static final long FADE_OUT_DELAY_ALPHA_MILLIS = 130;
+ private static final long FADE_OUT_DURATION_ALPHA_MILLIS = 170;
+
+ private static final float FADE_SCALE = 1.2f;
+
+ private final Context context;
+ private final Drawable payload;
+ private final long puckUpDurationMillis;
+ private final long puckUpDelayMillis;
+
+ private View puck;
+ private View payloadView;
+ private View answerHintContainer;
+ private AnimatorSet answerGestureHintAnim;
+
+ public EventAnswerHint(
+ @NonNull Context context,
+ @NonNull Drawable payload,
+ long puckUpDurationMillis,
+ long puckUpDelayMillis) {
+ this.context = Assert.isNotNull(context);
+ this.payload = Assert.isNotNull(payload);
+ this.puckUpDurationMillis = puckUpDurationMillis;
+ this.puckUpDelayMillis = puckUpDelayMillis;
+ }
+
+ @Override
+ public void onCreateView(
+ LayoutInflater inflater, ViewGroup container, View puck, TextView hintText) {
+ this.puck = puck;
+ View view = inflater.inflate(R.layout.event_hint, container, true);
+ answerHintContainer = view.findViewById(R.id.answer_hint_container);
+ payloadView = view.findViewById(R.id.payload);
+ hintText.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.hint_text_size));
+ ((ImageView) payloadView).setImageDrawable(payload);
+ }
+
+ @Override
+ public void onBounceStart() {
+ if (answerGestureHintAnim == null) {
+
+ answerGestureHintAnim = new AnimatorSet();
+ answerHintContainer.setY(puck.getY() + getDimension(R.dimen.hint_initial_offset));
+
+ Animator fadeIn = createFadeIn();
+
+ Animator swipeUp =
+ ObjectAnimator.ofFloat(
+ answerHintContainer,
+ View.TRANSLATION_Y,
+ puck.getY() - getDimension(R.dimen.hint_offset));
+ swipeUp.setInterpolator(new FastOutSlowInInterpolator());
+ swipeUp.setDuration(SWIPE_UP_DURATION_ALPHA_MILLIS);
+
+ Animator fadeOut = createFadeOut();
+
+ answerGestureHintAnim.play(fadeIn).after(puckUpDelayMillis);
+ answerGestureHintAnim.play(swipeUp).after(fadeIn);
+ // The fade out should start fading the alpha just as the puck is dropping. Scaling will start
+ // a bit earlier.
+ answerGestureHintAnim
+ .play(fadeOut)
+ .after(puckUpDelayMillis + puckUpDurationMillis - FADE_OUT_DELAY_ALPHA_MILLIS);
+
+ fadeIn.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ payloadView.setAlpha(0);
+ payloadView.setScaleX(1);
+ payloadView.setScaleY(1);
+ answerHintContainer.setY(puck.getY() + getDimension(R.dimen.hint_initial_offset));
+ answerHintContainer.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ answerGestureHintAnim.start();
+ }
+
+ private Animator createFadeIn() {
+ AnimatorSet set = new AnimatorSet();
+ set.play(createFadeInScaleAndAlpha(payloadView));
+ return set;
+ }
+
+ private static Animator createFadeInScaleAndAlpha(View target) {
+ Animator scale =
+ createUniformScaleAnimator(
+ target,
+ FADE_SCALE,
+ 1.0f,
+ FADE_IN_DURATION_SCALE_MILLIS,
+ FADE_IN_DELAY_SCALE_MILLIS,
+ new LinearInterpolator());
+ Animator alpha =
+ createAlphaAnimator(
+ target,
+ 0f,
+ 1.0f,
+ FADE_IN_DURATION_ALPHA_MILLIS,
+ FADE_IN_DELAY_ALPHA_MILLIS,
+ new LinearInterpolator());
+ AnimatorSet set = new AnimatorSet();
+ set.play(scale).with(alpha);
+ return set;
+ }
+
+ private Animator createFadeOut() {
+ AnimatorSet set = new AnimatorSet();
+ set.play(createFadeOutScaleAndAlpha(payloadView, FADE_OUT_DELAY_SCALE_SMALL_MILLIS));
+ return set;
+ }
+
+ private static Animator createFadeOutScaleAndAlpha(View target, long scaleDelay) {
+ Animator scale =
+ createUniformScaleAnimator(
+ target,
+ 1.0f,
+ FADE_SCALE,
+ FADE_OUT_DURATION_SCALE_MILLIS,
+ scaleDelay,
+ new LinearInterpolator());
+ Animator alpha =
+ createAlphaAnimator(
+ target,
+ 01.0f,
+ 0.0f,
+ FADE_OUT_DURATION_ALPHA_MILLIS,
+ FADE_OUT_DELAY_ALPHA_MILLIS,
+ new LinearInterpolator());
+ AnimatorSet set = new AnimatorSet();
+ set.play(scale).with(alpha);
+ return set;
+ }
+
+ @Override
+ public void onBounceEnd() {
+ if (answerGestureHintAnim != null) {
+ answerGestureHintAnim.end();
+ answerGestureHintAnim = null;
+ answerHintContainer.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onAnswered() {
+ // Do nothing
+ }
+
+ private float getDimension(@DimenRes int id) {
+ return context.getResources().getDimension(id);
+ }
+
+ private static Animator createUniformScaleAnimator(
+ View target,
+ float scaleBegin,
+ float scaleEnd,
+ long duration,
+ long delay,
+ Interpolator interpolator) {
+ Animator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, scaleBegin, scaleEnd);
+ Animator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, scaleBegin, scaleEnd);
+ scaleX.setDuration(duration);
+ scaleY.setDuration(duration);
+ scaleX.setInterpolator(interpolator);
+ scaleY.setInterpolator(interpolator);
+ AnimatorSet set = new AnimatorSet();
+ set.play(scaleX).with(scaleY).after(delay);
+ return set;
+ }
+
+ private static Animator createAlphaAnimator(
+ View target, float begin, float end, long duration, long delay, Interpolator interpolator) {
+ Animator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, begin, end);
+ alpha.setDuration(duration);
+ alpha.setInterpolator(interpolator);
+ alpha.setStartDelay(delay);
+ return alpha;
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/hint/EventPayloadLoader.java b/java/com/android/incallui/answer/impl/hint/EventPayloadLoader.java
new file mode 100644
index 000000000..09e3bedf2
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/EventPayloadLoader.java
@@ -0,0 +1,30 @@
+/*
+ * 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.answer.impl.hint;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import java.util.TimeZone;
+
+/** Loads a {@link Drawable} payload for the {@link EventAnswerHint} if it should be displayed. */
+public interface EventPayloadLoader {
+ @Nullable
+ Drawable loadPayload(
+ @NonNull Context context, long currentTimeUtcMillis, @NonNull TimeZone timeZone);
+}
diff --git a/java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java b/java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java
new file mode 100644
index 000000000..bd8d73645
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui.answer.impl.hint;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION_CODES;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.ConfigProvider;
+import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.common.LogUtil;
+import java.io.InputStream;
+import java.util.TimeZone;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/** Decrypt the event payload to be shown if in a specific time range and the key is received. */
+@TargetApi(VERSION_CODES.M)
+public final class EventPayloadLoaderImpl implements EventPayloadLoader {
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String CONFIG_EVENT_KEY = "event_key";
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String CONFIG_EVENT_BINARY = "event_binary";
+
+ // Time is stored as a UTC UNIX timestamp in milliseconds, but interpreted as local time.
+ // For example, 946684800 (2000/1/1 00:00:00 @UTC) is the new year midnight at every timezone.
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String CONFIG_EVENT_START_UTC_AS_LOCAL_MILLIS = "event_time_start_millis";
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String CONFIG_EVENT_TIME_END_UTC_AS_LOCAL_MILLIS = "event_time_end_millis";
+
+ @Override
+ @Nullable
+ public Drawable loadPayload(
+ @NonNull Context context, long currentTimeUtcMillis, @NonNull TimeZone timeZone) {
+ Assert.isNotNull(context);
+ Assert.isNotNull(timeZone);
+ ConfigProvider configProvider = ConfigProviderBindings.get(context);
+
+ String pbeKey = configProvider.getString(CONFIG_EVENT_KEY, null);
+ if (pbeKey == null) {
+ return null;
+ }
+ long timeRangeStart = configProvider.getLong(CONFIG_EVENT_START_UTC_AS_LOCAL_MILLIS, 0);
+ long timeRangeEnd = configProvider.getLong(CONFIG_EVENT_TIME_END_UTC_AS_LOCAL_MILLIS, 0);
+
+ String eventBinary = configProvider.getString(CONFIG_EVENT_BINARY, null);
+ if (eventBinary == null) {
+ return null;
+ }
+
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ if (!preferences.getBoolean(
+ EventSecretCodeListener.EVENT_ENABLED_WITH_SECRET_CODE_KEY, false)) {
+ long localTimestamp = currentTimeUtcMillis + timeZone.getRawOffset();
+
+ if (localTimestamp < timeRangeStart) {
+ return null;
+ }
+
+ if (localTimestamp > timeRangeEnd) {
+ return null;
+ }
+ }
+
+ // Use openssl aes-128-cbc -in <input> -out <output> -pass <PBEKey> to generate the asset
+ try (InputStream input = context.getAssets().open(eventBinary)) {
+ byte[] encryptedFile = new byte[input.available()];
+ input.read(encryptedFile);
+
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
+
+ byte[] salt = new byte[8];
+ System.arraycopy(encryptedFile, 8, salt, 0, 8);
+ SecretKey key =
+ SecretKeyFactory.getInstance("PBEWITHMD5AND128BITAES-CBC-OPENSSL", "BC")
+ .generateSecret(new PBEKeySpec(pbeKey.toCharArray(), salt, 100));
+ cipher.init(Cipher.DECRYPT_MODE, key);
+
+ byte[] decryptedFile = cipher.doFinal(encryptedFile, 16, encryptedFile.length - 16);
+
+ return new BitmapDrawable(
+ context.getResources(),
+ BitmapFactory.decodeByteArray(decryptedFile, 0, decryptedFile.length));
+ } catch (Exception e) {
+ // Avoid crashing dialer for any reason.
+ LogUtil.e("EventPayloadLoader.loadPayload", "error decrypting payload:", e);
+ return null;
+ }
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/hint/EventSecretCodeListener.java b/java/com/android/incallui/answer/impl/hint/EventSecretCodeListener.java
new file mode 100644
index 000000000..7cf4054a9
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/EventSecretCodeListener.java
@@ -0,0 +1,67 @@
+/*
+ * 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.hint;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.widget.Toast;
+import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.nano.DialerImpression;
+
+/**
+ * Listen to the broadcast when the user dials "*#*#[number]#*#*" to toggle the event answer hint.
+ */
+public class EventSecretCodeListener extends BroadcastReceiver {
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String CONFIG_EVENT_SECRET_CODE = "event_secret_code";
+
+ public static final String EVENT_ENABLED_WITH_SECRET_CODE_KEY = "event_enabled_with_secret_code";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String host = intent.getData().getHost();
+ String secretCode =
+ ConfigProviderBindings.get(context).getString(CONFIG_EVENT_SECRET_CODE, null);
+ if (secretCode == null) {
+ return;
+ }
+ if (!TextUtils.equals(secretCode, host)) {
+ return;
+ }
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean wasEnabled = preferences.getBoolean(EVENT_ENABLED_WITH_SECRET_CODE_KEY, false);
+ if (wasEnabled) {
+ preferences.edit().putBoolean(EVENT_ENABLED_WITH_SECRET_CODE_KEY, false).apply();
+ Toast.makeText(context, R.string.event_deactivated, Toast.LENGTH_SHORT).show();
+ Logger.get(context).logImpression(DialerImpression.Type.EVENT_ANSWER_HINT_DEACTIVATED);
+ LogUtil.i("EventSecretCodeListener.onReceive", "EventAnswerHint disabled");
+ } else {
+ preferences.edit().putBoolean(EVENT_ENABLED_WITH_SECRET_CODE_KEY, true).apply();
+ Toast.makeText(context, R.string.event_activated, Toast.LENGTH_SHORT).show();
+ Logger.get(context).logImpression(DialerImpression.Type.EVENT_ANSWER_HINT_ACTIVATED);
+ LogUtil.i("EventSecretCodeListener.onReceive", "EventAnswerHint enabled");
+ }
+ }
+}
diff --git a/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_large.xml b/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_large.xml
new file mode 100644
index 000000000..f585ce5c9
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_large.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00C853"/>
+</shape> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_mid.xml b/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_mid.xml
new file mode 100644
index 000000000..f585ce5c9
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_mid.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00C853"/>
+</shape> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_small.xml b/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_small.xml
new file mode 100644
index 000000000..6a24d6a5f
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/drawable/answer_hint_small.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="#00C853"/>
+ <stroke android:color="#00C853" android:width="2dp"/>
+ </shape> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/hint/res/layout/dot_hint.xml b/java/com/android/incallui/answer/impl/hint/res/layout/dot_hint.xml
new file mode 100644
index 000000000..84b10e736
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/layout/dot_hint.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/answer_hint_container"
+ android:layout_width="160dp"
+ android:layout_height="160dp"
+ android:layout_gravity="center_horizontal"
+ android:visibility="gone">
+ <ImageView
+ android:id="@+id/answer_hint_large"
+ android:layout_width="@dimen/hint_large_begin_size"
+ android:layout_height="@dimen/hint_large_begin_size"
+ android:layout_gravity="center"
+ android:alpha="0"
+ android:src="@drawable/answer_hint_large"/>
+ <ImageView
+ android:id="@+id/answer_hint_mid"
+ android:layout_width="@dimen/hint_mid_begin_size"
+ android:layout_height="@dimen/hint_mid_begin_size"
+ android:src="@drawable/answer_hint_mid"
+ android:alpha="0"
+ android:layout_gravity="center"/>
+ <ImageView
+ android:id="@+id/answer_hint_small"
+ android:layout_width="@dimen/hint_small_begin_size"
+ android:layout_height="@dimen/hint_small_begin_size"
+ android:src="@drawable/answer_hint_small"
+ android:alpha="0"
+ android:layout_gravity="center" />
+</FrameLayout> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/hint/res/layout/event_hint.xml b/java/com/android/incallui/answer/impl/hint/res/layout/event_hint.xml
new file mode 100644
index 000000000..d505014c1
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/layout/event_hint.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/answer_hint_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:visibility="gone">
+ <ImageView
+ android:id="@+id/payload"
+ android:layout_width="191dp"
+ android:layout_height="773dp"
+ android:layout_gravity="center"
+ android:alpha="0"
+ android:rotation="-30"
+ android:transformPivotY="90dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"/>
+</FrameLayout> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/hint/res/values/dimens.xml b/java/com/android/incallui/answer/impl/hint/res/values/dimens.xml
new file mode 100644
index 000000000..d86084b74
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/values/dimens.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="hint_text_size">18sp</dimen>
+ <dimen name="hint_initial_offset">-100dp</dimen>
+ <dimen name="hint_offset">300dp</dimen>
+ <dimen name="hint_small_begin_size">50dp</dimen>
+ <dimen name="hint_small_end_size">42dp</dimen>
+ <dimen name="hint_mid_begin_size">56dp</dimen>
+ <dimen name="hint_mid_end_size">64dp</dimen>
+ <dimen name="hint_large_begin_size">64dp</dimen>
+ <dimen name="hint_large_end_size">160dp</dimen>
+</resources> \ No newline at end of file
diff --git a/java/com/android/incallui/answer/impl/hint/res/values/strings.xml b/java/com/android/incallui/answer/impl/hint/res/values/strings.xml
new file mode 100644
index 000000000..d76021ae1
--- /dev/null
+++ b/java/com/android/incallui/answer/impl/hint/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="event_activated">Event Activated</string>
+ <string name="event_deactivated">Event Deactvated</string>
+</resources> \ No newline at end of file