From 71a22dc081e458706f07beb1684087dc4a6aedf5 Mon Sep 17 00:00:00 2001 From: wangqi Date: Mon, 21 May 2018 12:29:32 -0700 Subject: Add promotion module. Refactor Duo disclosure card to general promotion card. Bug: 78905507 Test: unit tests PiperOrigin-RevId: 197436677 Change-Id: I511c39308cadfb96ee4519b71ca29b75d0e6750b --- .../android/dialer/promotion/AndroidManifest.xml | 16 --- java/com/android/dialer/promotion/Promotion.java | 36 +++++- .../dialer/promotion/PromotionComponent.java | 43 +++++++ .../android/dialer/promotion/PromotionManager.java | 67 +++++++++++ .../com/android/dialer/promotion/RttPromotion.java | 84 ------------- .../dialer/promotion/impl/AndroidManifest.xml | 23 ++++ .../dialer/promotion/impl/DuoPromotion.java | 132 +++++++++++++++++++++ .../dialer/promotion/impl/PromotionModule.java | 36 ++++++ .../dialer/promotion/impl/RttPromotion.java | 94 +++++++++++++++ .../dialer/promotion/impl/res/values/strings.xml | 35 ++++++ .../dialer/promotion/res/values/strings.xml | 26 ---- 11 files changed, 462 insertions(+), 130 deletions(-) delete mode 100644 java/com/android/dialer/promotion/AndroidManifest.xml create mode 100644 java/com/android/dialer/promotion/PromotionComponent.java create mode 100644 java/com/android/dialer/promotion/PromotionManager.java delete mode 100644 java/com/android/dialer/promotion/RttPromotion.java create mode 100644 java/com/android/dialer/promotion/impl/AndroidManifest.xml create mode 100644 java/com/android/dialer/promotion/impl/DuoPromotion.java create mode 100644 java/com/android/dialer/promotion/impl/PromotionModule.java create mode 100644 java/com/android/dialer/promotion/impl/RttPromotion.java create mode 100644 java/com/android/dialer/promotion/impl/res/values/strings.xml delete mode 100644 java/com/android/dialer/promotion/res/values/strings.xml (limited to 'java/com/android/dialer/promotion') diff --git a/java/com/android/dialer/promotion/AndroidManifest.xml b/java/com/android/dialer/promotion/AndroidManifest.xml deleted file mode 100644 index bd85b104f..000000000 --- a/java/com/android/dialer/promotion/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/java/com/android/dialer/promotion/Promotion.java b/java/com/android/dialer/promotion/Promotion.java index 3cd16d4a6..176606ff4 100644 --- a/java/com/android/dialer/promotion/Promotion.java +++ b/java/com/android/dialer/promotion/Promotion.java @@ -17,23 +17,51 @@ package com.android.dialer.promotion; import android.support.annotation.DrawableRes; +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** Interface for promotion bottom sheet. */ public interface Promotion { - /** Returns if this promotion should be shown. */ - boolean shouldShow(); + /** + * Type of promotion, which means promotion should be shown as a card in {@link + * android.support.v7.widget.RecyclerView} or {@link + * android.support.design.bottomsheet.BottomSheetBehavior}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PromotionType.CARD, PromotionType.BOTTOM_SHEET}) + @interface PromotionType { + /** Shown as card in call log or voicemail tab. */ + int CARD = 1; - /** Sets to show this promotion. */ - void setShouldShow(boolean shouldShow); + /** Shown as bottom sheet. */ + int BOTTOM_SHEET = 2; + } + + /** Returns {@link PromotionType} for this promotion. */ + @PromotionType + int getType(); + + /** + * Returns if this promotion should be shown. This usually means the promotion is enabled and not + * dismissed yet. + */ + boolean isEligibleToBeShown(); + + /** Called when this promotion is first time viewed by user. */ + default void onViewed() {} /** Dismisses this promotion. This is called when user acknowledged the promotion. */ void dismiss(); + /** Returns title text of the promotion. */ CharSequence getTitle(); + /** Returns details text of the promotion. */ CharSequence getDetails(); + /** Returns resource id of the icon for the promotion. */ @DrawableRes int getIconRes(); } diff --git a/java/com/android/dialer/promotion/PromotionComponent.java b/java/com/android/dialer/promotion/PromotionComponent.java new file mode 100644 index 000000000..caf10dbb0 --- /dev/null +++ b/java/com/android/dialer/promotion/PromotionComponent.java @@ -0,0 +1,43 @@ +/* + * 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.dialer.promotion; + +import android.content.Context; +import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; +import com.google.common.collect.ImmutableList; +import dagger.Subcomponent; + +/** Component for promotion. */ +@Subcomponent +public abstract class PromotionComponent { + + public abstract PromotionManager promotionManager(); + + public abstract ImmutableList priorityPromotionList(); + + public static PromotionComponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .promotionComponent(); + } + + /** Used to refer to the root application component. */ + @IncludeInDialerRoot + public interface HasComponent { + PromotionComponent promotionComponent(); + } +} diff --git a/java/com/android/dialer/promotion/PromotionManager.java b/java/com/android/dialer/promotion/PromotionManager.java new file mode 100644 index 000000000..a86a745ad --- /dev/null +++ b/java/com/android/dialer/promotion/PromotionManager.java @@ -0,0 +1,67 @@ +/* + * 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.dialer.promotion; + +import com.android.dialer.promotion.Promotion.PromotionType; +import com.google.common.collect.ImmutableList; +import java.util.Optional; +import javax.inject.Inject; + +/** + * A class to manage all promotion cards/bottom sheet. + * + *

Only one promotion with highest priority will be shown at a time no matter type. So if there + * are one card and one bottom sheet promotion, either one will be shown instead of both. + */ +public final class PromotionManager { + + /** Promotion priority order list. Promotions with higher priority must be added first. */ + private ImmutableList priorityPromotionList; + + @Inject + public PromotionManager(ImmutableList priorityPromotionList) { + this.priorityPromotionList = priorityPromotionList; + } + + /** + * Returns promotion should show with highest priority. {@link Optional#empty()} if no promotion + * should be shown with given {@link PromotionType}. + * + *

e.g. if FooPromotion(card, high priority) and BarPromotion(bottom sheet, low priority) are + * both enabled, getHighestPriorityPromotion(CARD) returns Optional.of(FooPromotion) but + * getHighestPriorityPromotion(BOTTOM_SHEET) returns {@link Optional#empty()}. + * + *

Currently it only supports promotion in call log tab. + * + *

TODO(wangqi): add support for other tabs. + */ + @SuppressWarnings("AndroidApiChecker") // Use of optional + public Optional getHighestPriorityPromotion(@PromotionType int type) { + for (Promotion promotion : priorityPromotionList) { + if (promotion.isEligibleToBeShown()) { + if (promotion.getType() == type) { + return Optional.of(promotion); + } else { + // Returns empty promotion since it's not the type looking for and only one promotion + // should be shown at a time. + return Optional.empty(); + } + } + } + return Optional.empty(); + } +} diff --git a/java/com/android/dialer/promotion/RttPromotion.java b/java/com/android/dialer/promotion/RttPromotion.java deleted file mode 100644 index feb6e4734..000000000 --- a/java/com/android/dialer/promotion/RttPromotion.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.dialer.promotion; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.DrawableRes; -import com.android.dialer.common.LogUtil; -import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.spannable.ContentWithLearnMoreSpanner; -import com.android.dialer.storage.StorageComponent; - -/** RTT promotion. */ -public final class RttPromotion implements Promotion { - private static final String SHARED_PREFERENCE_KEY_ENABLED = "rtt_promotion_enabled"; - private static final String SHARED_PREFERENCE_KEY_DISMISSED = "rtt_promotion_dismissed"; - private final Context appContext; - - public RttPromotion(Context context) { - appContext = context.getApplicationContext(); - } - - @Override - public boolean shouldShow() { - SharedPreferences sharedPreferences = StorageComponent.get(appContext).unencryptedSharedPrefs(); - return sharedPreferences.getBoolean(SHARED_PREFERENCE_KEY_ENABLED, false) - && !sharedPreferences.getBoolean(SHARED_PREFERENCE_KEY_DISMISSED, false); - } - - @Override - public CharSequence getTitle() { - return appContext.getString(R.string.rtt_promotion_title); - } - - @Override - public CharSequence getDetails() { - return new ContentWithLearnMoreSpanner(appContext) - .create( - appContext.getString(R.string.rtt_promotion_details), - ConfigProviderBindings.get(appContext) - .getString( - "rtt_promo_learn_more_link_full_url", - "http://support.google.com/pixelphone/?p=dialer_rtt")); - } - - @Override - @DrawableRes - public int getIconRes() { - return R.drawable.quantum_ic_rtt_vd_theme_24; - } - - @Override - public void setShouldShow(boolean shouldShow) { - LogUtil.i("RttPromotion.setShouldShow", "shouldShow: %b", shouldShow); - StorageComponent.get(appContext) - .unencryptedSharedPrefs() - .edit() - .putBoolean(SHARED_PREFERENCE_KEY_ENABLED, shouldShow) - .apply(); - } - - @Override - public void dismiss() { - StorageComponent.get(appContext) - .unencryptedSharedPrefs() - .edit() - .putBoolean(SHARED_PREFERENCE_KEY_DISMISSED, true) - .apply(); - } -} diff --git a/java/com/android/dialer/promotion/impl/AndroidManifest.xml b/java/com/android/dialer/promotion/impl/AndroidManifest.xml new file mode 100644 index 000000000..c938b9a37 --- /dev/null +++ b/java/com/android/dialer/promotion/impl/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/java/com/android/dialer/promotion/impl/DuoPromotion.java b/java/com/android/dialer/promotion/impl/DuoPromotion.java new file mode 100644 index 000000000..750e4a196 --- /dev/null +++ b/java/com/android/dialer/promotion/impl/DuoPromotion.java @@ -0,0 +1,132 @@ +/* + * 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.dialer.promotion.impl; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.VisibleForTesting; +import com.android.dialer.configprovider.ConfigProvider; +import com.android.dialer.duo.Duo; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.promotion.Promotion; +import com.android.dialer.spannable.ContentWithLearnMoreSpanner; +import com.android.dialer.storage.Unencrypted; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; + +/** Duo promotion. */ +final class DuoPromotion implements Promotion { + private static final String SHARED_PREF_KEY_DUO_DISCLOSURE_DISMISSED = "duo_disclosure_dismissed"; + + private static final String SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS = + "duo_disclosure_first_viewed_time_ms"; + @VisibleForTesting static final String FLAG_SHOW_DUO_DISCLOSURE = "show_duo_disclosure"; + + @VisibleForTesting + static final String FLAG_DUO_DISCLOSURE_AUTO_DISMISS_AFTER_VIEWED_TIME_MILLIS = + "show_duo_disclosure_auto_dismiss_after_viewed_time_millis"; + + private final Context appContext; + private final ConfigProvider configProvider; + private final Duo duo; + private final SharedPreferences sharedPreferences; + + @Inject + DuoPromotion( + @ApplicationContext Context context, + ConfigProvider configProvider, + Duo duo, + @Unencrypted SharedPreferences sharedPreferences) { + this.appContext = context; + this.configProvider = configProvider; + this.duo = duo; + this.sharedPreferences = sharedPreferences; + } + + @Override + public int getType() { + return PromotionType.CARD; + } + + @Override + public boolean isEligibleToBeShown() { + if (!configProvider.getBoolean(FLAG_SHOW_DUO_DISCLOSURE, false)) { + return false; + } + // Don't show the Duo disclosure card if + // (1) Duo integration is not enabled on the device, or + // (2) Duo is not activated. + if (!duo.isEnabled(appContext) || !duo.isActivated(appContext)) { + return false; + } + + // Don't show the Duo disclosure card if it has been dismissed. + if (sharedPreferences.getBoolean(SHARED_PREF_KEY_DUO_DISCLOSURE_DISMISSED, false)) { + return false; + } + + // At this point, Duo is activated and the disclosure card hasn't been dismissed. + // We should show the card if it has never been viewed by the user. + if (!sharedPreferences.contains(SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS)) { + return true; + } + + // At this point, the card has been viewed but not dismissed. + // We should not show the card if it has been viewed for more than 1 day. + long duoDisclosureFirstViewTimeMillis = + sharedPreferences.getLong(SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS, 0); + return System.currentTimeMillis() - duoDisclosureFirstViewTimeMillis + <= configProvider.getLong( + FLAG_DUO_DISCLOSURE_AUTO_DISMISS_AFTER_VIEWED_TIME_MILLIS, TimeUnit.DAYS.toMillis(1)); + } + + @Override + public void onViewed() { + if (!sharedPreferences.contains(SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS)) { + sharedPreferences + .edit() + .putLong( + SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS, System.currentTimeMillis()) + .apply(); + } + } + + @Override + public void dismiss() { + sharedPreferences.edit().putBoolean(SHARED_PREF_KEY_DUO_DISCLOSURE_DISMISSED, true).apply(); + } + + @Override + public CharSequence getTitle() { + return appContext.getString(R.string.duo_disclosure_title); + } + + @Override + public CharSequence getDetails() { + return new ContentWithLearnMoreSpanner(appContext) + .create( + appContext.getString(R.string.duo_disclosure_details), + configProvider.getString( + "duo_disclosure_link_full_url", + "http://support.google.com/pixelphone/?p=dialer_duo")); + } + + @Override + public int getIconRes() { + return duo.getLogo(); + } +} diff --git a/java/com/android/dialer/promotion/impl/PromotionModule.java b/java/com/android/dialer/promotion/impl/PromotionModule.java new file mode 100644 index 000000000..f0908780c --- /dev/null +++ b/java/com/android/dialer/promotion/impl/PromotionModule.java @@ -0,0 +1,36 @@ +/* + * 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.dialer.promotion.impl; + +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; +import com.android.dialer.promotion.Promotion; +import com.google.common.collect.ImmutableList; +import dagger.Module; +import dagger.Provides; + +/** Module for Promotion. */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) +@Module +public abstract class PromotionModule { + + @Provides + static ImmutableList providePriorityPromotionList( + RttPromotion rttPromotion, DuoPromotion duoPromotion) { + return ImmutableList.of(rttPromotion, duoPromotion); + } +} diff --git a/java/com/android/dialer/promotion/impl/RttPromotion.java b/java/com/android/dialer/promotion/impl/RttPromotion.java new file mode 100644 index 000000000..f0f7f546b --- /dev/null +++ b/java/com/android/dialer/promotion/impl/RttPromotion.java @@ -0,0 +1,94 @@ +/* + * 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.dialer.promotion.impl; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.DrawableRes; +import com.android.dialer.common.LogUtil; +import com.android.dialer.configprovider.ConfigProvider; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.promotion.Promotion; +import com.android.dialer.spannable.ContentWithLearnMoreSpanner; +import com.android.dialer.storage.StorageComponent; +import com.android.dialer.storage.Unencrypted; +import javax.inject.Inject; + +/** RTT promotion. */ +public final class RttPromotion implements Promotion { + private static final String SHARED_PREFERENCE_KEY_ENABLED = "rtt_promotion_enabled"; + private static final String SHARED_PREFERENCE_KEY_DISMISSED = "rtt_promotion_dismissed"; + private final Context appContext; + private final SharedPreferences sharedPreferences; + private final ConfigProvider configProvider; + + @Override + public int getType() { + return PromotionType.BOTTOM_SHEET; + } + + @Inject + RttPromotion( + @ApplicationContext Context context, + @Unencrypted SharedPreferences sharedPreferences, + ConfigProvider configProvider) { + appContext = context; + this.sharedPreferences = sharedPreferences; + this.configProvider = configProvider; + } + + @Override + public boolean isEligibleToBeShown() { + return sharedPreferences.getBoolean(SHARED_PREFERENCE_KEY_ENABLED, false) + && !sharedPreferences.getBoolean(SHARED_PREFERENCE_KEY_DISMISSED, false); + } + + @Override + public CharSequence getTitle() { + return appContext.getString(R.string.rtt_promotion_title); + } + + @Override + public CharSequence getDetails() { + return new ContentWithLearnMoreSpanner(appContext) + .create( + appContext.getString(R.string.rtt_promotion_details), + configProvider.getString( + "rtt_promo_learn_more_link_full_url", + "http://support.google.com/pixelphone/?p=dialer_rtt")); + } + + @Override + @DrawableRes + public int getIconRes() { + return R.drawable.quantum_ic_rtt_vd_theme_24; + } + + public static void setEnabled(Context context) { + LogUtil.enterBlock("RttPromotion.setEnabled"); + StorageComponent.get(context) + .unencryptedSharedPrefs() + .edit() + .putBoolean(SHARED_PREFERENCE_KEY_ENABLED, true) + .apply(); + } + + @Override + public void dismiss() { + sharedPreferences.edit().putBoolean(SHARED_PREFERENCE_KEY_DISMISSED, true).apply(); + } +} diff --git a/java/com/android/dialer/promotion/impl/res/values/strings.xml b/java/com/android/dialer/promotion/impl/res/values/strings.xml new file mode 100644 index 000000000..899074932 --- /dev/null +++ b/java/com/android/dialer/promotion/impl/res/values/strings.xml @@ -0,0 +1,35 @@ + + + + + Real-time text messaging within a call + + + RTT assists callers who are deaf, hard of hearing, have a speech + disability, or need more than voice alone. RTT messaging transcripts are stored on your device + in the call history. %1$s + + + + Make video calls with Duo + + + + + Google Duo video calling lets you chat with friends and family face-to-face. Data charges may apply. %1$s + + diff --git a/java/com/android/dialer/promotion/res/values/strings.xml b/java/com/android/dialer/promotion/res/values/strings.xml deleted file mode 100644 index 633671546..000000000 --- a/java/com/android/dialer/promotion/res/values/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Real-time text messaging within a call - - - RTT assists callers who are deaf, hard of hearing, have a speech - disability, or need more than voice alone. RTT messaging transcripts are stored on your device - in the call history. %1$s - - -- cgit v1.2.3