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 --- .../calllog/ui/DuoDisclosureCardViewHolder.java | 64 ---------- .../dialer/calllog/ui/NewCallLogAdapter.java | 142 ++++++--------------- .../dialer/calllog/ui/NewCallLogFragment.java | 12 +- .../dialer/calllog/ui/PromotionCardViewHolder.java | 63 +++++++++ .../layout/new_call_log_duo_disclosure_card.xml | 83 ------------ .../ui/res/layout/new_call_log_promotion_card.xml | 83 ++++++++++++ .../dialer/calllog/ui/res/values/strings.xml | 11 -- 7 files changed, 196 insertions(+), 262 deletions(-) delete mode 100644 java/com/android/dialer/calllog/ui/DuoDisclosureCardViewHolder.java create mode 100644 java/com/android/dialer/calllog/ui/PromotionCardViewHolder.java delete mode 100644 java/com/android/dialer/calllog/ui/res/layout/new_call_log_duo_disclosure_card.xml create mode 100644 java/com/android/dialer/calllog/ui/res/layout/new_call_log_promotion_card.xml (limited to 'java/com/android/dialer/calllog') diff --git a/java/com/android/dialer/calllog/ui/DuoDisclosureCardViewHolder.java b/java/com/android/dialer/calllog/ui/DuoDisclosureCardViewHolder.java deleted file mode 100644 index 6b9112789..000000000 --- a/java/com/android/dialer/calllog/ui/DuoDisclosureCardViewHolder.java +++ /dev/null @@ -1,64 +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.calllog.ui; - -import android.content.Context; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.text.method.LinkMovementMethod; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.duo.DuoComponent; -import com.android.dialer.spannable.ContentWithLearnMoreSpanner; - -/** ViewHolder for {@link NewCallLogAdapter} to display the Duo disclosure card. */ -public class DuoDisclosureCardViewHolder extends ViewHolder { - - private final Button okButton; - - DuoDisclosureCardViewHolder(View itemView) { - super(itemView); - - Context context = itemView.getContext(); - - // Set the Duo logo. - ImageView duoLogoView = itemView.findViewById(R.id.new_call_log_duo_disclosure_card_logo); - duoLogoView.setImageResource(DuoComponent.get(context).getDuo().getLogo()); - - // Set detailed text with a "learn more" link. - TextView cardDetailsView = itemView.findViewById(R.id.new_call_log_duo_disclosure_card_details); - cardDetailsView.setText( - new ContentWithLearnMoreSpanner(context) - .create( - context.getResources().getString(R.string.new_call_log_duo_disclosure_card_details), - ConfigProviderBindings.get(context) - .getString( - "duo_disclosure_link_full_url", - "http://support.google.com/pixelphone/?p=dialer_duo"))); - cardDetailsView.setMovementMethod(LinkMovementMethod.getInstance()); // make the link clickable - - // Obtain a reference to the "OK, got it" button. - okButton = itemView.findViewById(R.id.new_call_log_duo_disclosure_card_ok); - } - - void setDismissListener(OnClickListener listener) { - okButton.setOnClickListener(listener); - } -} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java index 501cf1657..58bf8c061 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java @@ -17,11 +17,9 @@ package com.android.dialer.calllog.ui; import android.app.Activity; import android.content.Context; -import android.content.SharedPreferences; import android.database.Cursor; import android.support.annotation.IntDef; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; @@ -29,37 +27,27 @@ import android.view.ViewGroup; import com.android.dialer.calllog.database.Coalescer; import com.android.dialer.calllogutils.CallLogDates; import com.android.dialer.common.Assert; -import com.android.dialer.duo.Duo; -import com.android.dialer.duo.DuoComponent; import com.android.dialer.logging.Logger; -import com.android.dialer.promotion.RttPromotion; -import com.android.dialer.storage.StorageComponent; +import com.android.dialer.promotion.Promotion; import com.android.dialer.time.Clock; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.TimeUnit; /** {@link RecyclerView.Adapter} for the new call log fragment. */ final class NewCallLogAdapter extends RecyclerView.Adapter { - @VisibleForTesting - 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"; - /** IntDef for the different types of rows that can be shown in the call log. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ - RowType.DUO_DISCLOSURE_CARD, + RowType.PROMOTION_CARD, RowType.HEADER_TODAY, RowType.HEADER_YESTERDAY, RowType.HEADER_OLDER, RowType.CALL_LOG_ENTRY }) @interface RowType { - /** The Duo disclosure card. */ - int DUO_DISCLOSURE_CARD = 1; + /** The promotion card. */ + int PROMOTION_CARD = 1; /** Header that displays "Today". */ int HEADER_TODAY = 2; @@ -78,14 +66,12 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { private final Activity activity; private final RealtimeRowProcessor realtimeRowProcessor; private final PopCounts popCounts = new PopCounts(); - private final SharedPreferences sharedPref; - private final OnScrollListenerForRecordingDuoDisclosureFirstViewTime - onScrollListenerForRecordingDuoDisclosureFirstViewTime; + @Nullable private final Promotion promotion; private Cursor cursor; - /** Position of the Duo disclosure card. Null when it should not be displayed. */ - @Nullable private Integer duoDisclosureCardPosition; + /** Position of the promotion card. Null when it should not be displayed. */ + @Nullable private Integer promotionCardPosition; /** Position of the "Today" header. Null when it should not be displayed. */ @Nullable private Integer todayHeaderPosition; @@ -96,14 +82,12 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { /** Position of the "Older" header. Null when it should not be displayed. */ @Nullable private Integer olderHeaderPosition; - NewCallLogAdapter(Activity activity, Cursor cursor, Clock clock) { + NewCallLogAdapter(Activity activity, Cursor cursor, Clock clock, @Nullable Promotion promotion) { this.activity = activity; this.cursor = cursor; this.clock = clock; this.realtimeRowProcessor = CallLogUiComponent.get(activity).realtimeRowProcessor(); - this.sharedPref = StorageComponent.get(activity).unencryptedSharedPrefs(); - this.onScrollListenerForRecordingDuoDisclosureFirstViewTime = - new OnScrollListenerForRecordingDuoDisclosureFirstViewTime(sharedPref, clock); + this.promotion = promotion; setCardAndHeaderPositions(); } @@ -126,11 +110,11 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { } private void setCardAndHeaderPositions() { - // Set the position for the Duo disclosure card if it should be shown. - duoDisclosureCardPosition = null; + // Set the position for the promotion card if it should be shown. + promotionCardPosition = null; int numCards = 0; - if (shouldShowDuoDisclosureCard()) { - duoDisclosureCardPosition = 0; + if (promotion != null && promotion.isEligibleToBeShown()) { + promotionCardPosition = 0; numCards++; } @@ -174,61 +158,26 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { !cursor.isAfterLast() ? numItemsInToday + numItemsInYesterday + numCards : null; } - private boolean shouldShowDuoDisclosureCard() { - if (new RttPromotion(activity).shouldShow()) { - 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. - Duo duo = DuoComponent.get(activity).getDuo(); - if (!duo.isEnabled(activity) || !duo.isActivated(activity)) { - return false; - } - - // Don't show the Duo disclosure card if it has been dismissed. - if (sharedPref.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 (!sharedPref.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 = - sharedPref.getLong(SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS, 0); - return clock.currentTimeMillis() - duoDisclosureFirstViewTimeMillis - <= TimeUnit.DAYS.toMillis(1); - } - @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); - // Register a OnScrollListener that records the timestamp at which the Duo disclosure is first - // viewed if - // (1) the Duo disclosure card should be shown, and - // (2) it hasn't been viewed yet. - if (shouldShowDuoDisclosureCard() - && !sharedPref.contains(SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS)) { - recyclerView.addOnScrollListener(onScrollListenerForRecordingDuoDisclosureFirstViewTime); + // Register a OnScrollListener that records when the promotion is viewed. + if (promotion != null && promotion.isEligibleToBeShown()) { + recyclerView.addOnScrollListener( + new OnScrollListenerForRecordingPromotionCardFirstViewTime(promotion)); } } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) { switch (viewType) { - case RowType.DUO_DISCLOSURE_CARD: - return new DuoDisclosureCardViewHolder( + case RowType.PROMOTION_CARD: + return new PromotionCardViewHolder( LayoutInflater.from(activity) .inflate( - R.layout.new_call_log_duo_disclosure_card, - viewGroup, - /* attachToRoot = */ false)); + R.layout.new_call_log_promotion_card, viewGroup, /* attachToRoot = */ false), + promotion); case RowType.HEADER_TODAY: case RowType.HEADER_YESTERDAY: case RowType.HEADER_OLDER: @@ -252,16 +201,11 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { public void onBindViewHolder(ViewHolder viewHolder, int position) { @RowType int viewType = getItemViewType(position); switch (viewType) { - case RowType.DUO_DISCLOSURE_CARD: - ((DuoDisclosureCardViewHolder) viewHolder) + case RowType.PROMOTION_CARD: + ((PromotionCardViewHolder) viewHolder) .setDismissListener( - unused -> { - StorageComponent.get(activity) - .unencryptedSharedPrefs() - .edit() - .putBoolean(SHARED_PREF_KEY_DUO_DISCLOSURE_DISMISSED, true) - .apply(); - notifyItemRemoved(duoDisclosureCardPosition); + () -> { + notifyItemRemoved(promotionCardPosition); setCardAndHeaderPositions(); }); break; @@ -277,7 +221,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { case RowType.CALL_LOG_ENTRY: NewCallLogViewHolder newCallLogViewHolder = (NewCallLogViewHolder) viewHolder; int previousCardAndHeaders = 0; - if (duoDisclosureCardPosition != null && position > duoDisclosureCardPosition) { + if (promotionCardPosition != null && position > promotionCardPosition) { previousCardAndHeaders++; } if (todayHeaderPosition != null && position > todayHeaderPosition) { @@ -301,8 +245,8 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { @Override @RowType public int getItemViewType(int position) { - if (duoDisclosureCardPosition != null && position == duoDisclosureCardPosition) { - return RowType.DUO_DISCLOSURE_CARD; + if (promotionCardPosition != null && position == promotionCardPosition) { + return RowType.PROMOTION_CARD; } if (todayHeaderPosition != null && position == todayHeaderPosition) { return RowType.HEADER_TODAY; @@ -321,7 +265,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { int numberOfCards = 0; int numberOfHeaders = 0; - if (duoDisclosureCardPosition != null) { + if (promotionCardPosition != null) { numberOfCards++; } if (todayHeaderPosition != null) { @@ -337,35 +281,27 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { } /** - * A {@link RecyclerView.OnScrollListener} that records the timestamp at which the Duo disclosure - * card is first viewed. + * A {@link RecyclerView.OnScrollListener} that records the timestamp at which the promotion card + * is first viewed. * *

We consider the card as viewed if the user scrolls the containing RecyclerView since such * action is a strong proof. */ - private static final class OnScrollListenerForRecordingDuoDisclosureFirstViewTime + private static final class OnScrollListenerForRecordingPromotionCardFirstViewTime extends RecyclerView.OnScrollListener { - private final SharedPreferences sharedPref; - private final Clock clock; + private final Promotion promotion; - OnScrollListenerForRecordingDuoDisclosureFirstViewTime( - SharedPreferences sharedPref, Clock clock) { - this.sharedPref = sharedPref; - this.clock = clock; + OnScrollListenerForRecordingPromotionCardFirstViewTime(Promotion promotion) { + this.promotion = promotion; } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - if (!sharedPref.contains(SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS) - && newState == RecyclerView.SCROLL_STATE_SETTLING) { - sharedPref - .edit() - .putLong( - SHARED_PREF_KEY_DUO_DISCLOSURE_FIRST_VIEW_TIME_MILLIS, clock.currentTimeMillis()) - .apply(); - - // Recording the timestamp is this listener's sole responsibility. + if (newState == RecyclerView.SCROLL_STATE_SETTLING) { + promotion.onViewed(); + + // Recording promotion is viewed is this listener's sole responsibility. // We can remove it from the containing RecyclerView after the job is done. recyclerView.removeOnScrollListener(this); } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 2feed4068..ec6e8a0fd 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -42,6 +42,8 @@ import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.metrics.jank.RecyclerViewJankLogger; +import com.android.dialer.promotion.Promotion.PromotionType; +import com.android.dialer.promotion.PromotionComponent; import com.android.dialer.util.PermissionsUtil; import com.android.dialer.widget.EmptyContentView; import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; @@ -289,6 +291,7 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback return new AnnotatedCallLogCursorLoader(Assert.isNotNull(getContext())); } + @SuppressWarnings("AndroidApiChecker") // Use of optional @Override public void onLoadFinished(Loader loader, Cursor newCursor) { LogUtil.enterBlock("NewCallLogFragment.onLoadFinished"); @@ -319,7 +322,14 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback // instead. Activity activity = Assert.isNotNull(getActivity()); recyclerView.setAdapter( - new NewCallLogAdapter(activity, coalescedCursor, System::currentTimeMillis)); + new NewCallLogAdapter( + activity, + coalescedCursor, + System::currentTimeMillis, + PromotionComponent.get(getContext()) + .promotionManager() + .getHighestPriorityPromotion(PromotionType.CARD) + .orElse(null))); } else { ((NewCallLogAdapter) recyclerView.getAdapter()).updateCursor(coalescedCursor); } diff --git a/java/com/android/dialer/calllog/ui/PromotionCardViewHolder.java b/java/com/android/dialer/calllog/ui/PromotionCardViewHolder.java new file mode 100644 index 000000000..c7d62ba16 --- /dev/null +++ b/java/com/android/dialer/calllog/ui/PromotionCardViewHolder.java @@ -0,0 +1,63 @@ +/* + * 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.calllog.ui; + +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.dialer.promotion.Promotion; + +/** ViewHolder for {@link NewCallLogAdapter} to display the Duo disclosure card. */ +public class PromotionCardViewHolder extends ViewHolder { + + /** Listener to be called when promotion card is dismissed. */ + interface DismissListener { + void onDismiss(); + } + + private final Button okButton; + private final Promotion promotion; + + PromotionCardViewHolder(View itemView, Promotion promotion) { + super(itemView); + this.promotion = promotion; + + ImageView iconView = itemView.findViewById(R.id.new_call_log_promotion_card_icon); + iconView.setImageResource(promotion.getIconRes()); + + TextView cardTitleView = itemView.findViewById(R.id.new_call_log_promotion_card_title); + cardTitleView.setText(promotion.getTitle()); + + TextView cardDetailsView = itemView.findViewById(R.id.new_call_log_promotion_card_details); + cardDetailsView.setText(promotion.getDetails()); + cardDetailsView.setMovementMethod(LinkMovementMethod.getInstance()); // make the link clickable + + // Obtain a reference to the "OK, got it" button. + okButton = itemView.findViewById(R.id.new_call_log_promotion_card_ok); + } + + void setDismissListener(DismissListener listener) { + okButton.setOnClickListener( + v -> { + promotion.dismiss(); + listener.onDismiss(); + }); + } +} diff --git a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_duo_disclosure_card.xml b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_duo_disclosure_card.xml deleted file mode 100644 index 93fd0ac40..000000000 --- a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_duo_disclosure_card.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - -