1 // Copyright 2020 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.app.video_tutorials; 6 7 import android.content.Context; 8 import android.content.Intent; 9 import android.view.ViewStub; 10 11 import androidx.annotation.VisibleForTesting; 12 13 import org.chromium.base.Callback; 14 import org.chromium.chrome.browser.feature_engagement.TrackerFactory; 15 import org.chromium.chrome.browser.flags.ChromeFeatureList; 16 import org.chromium.chrome.browser.image_fetcher.ImageFetcher; 17 import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig; 18 import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory; 19 import org.chromium.chrome.browser.profiles.Profile; 20 import org.chromium.chrome.browser.video_tutorials.FeatureType; 21 import org.chromium.chrome.browser.video_tutorials.Tutorial; 22 import org.chromium.chrome.browser.video_tutorials.VideoTutorialService; 23 import org.chromium.chrome.browser.video_tutorials.VideoTutorialServiceFactory; 24 import org.chromium.chrome.browser.video_tutorials.iph.VideoIPHCoordinator; 25 import org.chromium.chrome.browser.video_tutorials.iph.VideoTutorialIPHUtils; 26 import org.chromium.chrome.browser.video_tutorials.metrics.VideoTutorialMetrics; 27 import org.chromium.chrome.browser.video_tutorials.metrics.VideoTutorialMetrics.UserAction; 28 import org.chromium.components.browser_ui.util.GlobalDiscardableReferencePool; 29 import org.chromium.components.feature_engagement.Tracker; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 34 /** 35 * Handles all the logic required for showing the appropriate video tutorial IPH on new tab page. 36 * Queries the backend for the sorted list of available video tutorials, and shows the one not seen 37 * by user yet. Also responsible for showing the next tutorial IPH when one is consumed or 38 * dismissed. 39 */ 40 public class NewTabPageVideoIPHManager { 41 private Context mContext; 42 private Tracker mTracker; 43 private VideoIPHCoordinator mVideoIPHCoordinator; 44 private VideoTutorialService mVideoTutorialService; 45 46 /** 47 * Constructor. 48 * @param viewStub The {@link ViewStub} to be inflated to show the IPH. 49 * @param profile The associated profile. 50 */ NewTabPageVideoIPHManager(ViewStub viewStub, Profile profile)51 public NewTabPageVideoIPHManager(ViewStub viewStub, Profile profile) { 52 if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VIDEO_TUTORIALS)) return; 53 54 mContext = viewStub.getContext(); 55 mTracker = TrackerFactory.getTrackerForProfile(profile); 56 mVideoIPHCoordinator = createVideoIPHCoordinator( 57 viewStub, createImageFetcher(profile), this::onClickIPH, this::onDismissIPH); 58 mVideoTutorialService = VideoTutorialServiceFactory.getForProfile(profile); 59 mVideoTutorialService.getTutorials(this::onFetchTutorials); 60 } 61 onFetchTutorials(List<Tutorial> tutorials)62 private void onFetchTutorials(List<Tutorial> tutorials) { 63 if (tutorials.isEmpty()) return; 64 65 // Add the summary tutorial to the list. 66 List<Tutorial> tutorialsCopy = new ArrayList<>(tutorials); 67 mVideoTutorialService.getTutorial(FeatureType.SUMMARY, tutorial -> { 68 if (tutorial != null) tutorialsCopy.add(tutorial); 69 70 mTracker.addOnInitializedCallback(success -> { 71 if (!success) return; 72 showFirstEligibleIPH(tutorialsCopy); 73 }); 74 }); 75 } 76 showFirstEligibleIPH(List<Tutorial> tutorials)77 private void showFirstEligibleIPH(List<Tutorial> tutorials) { 78 for (Tutorial tutorial : tutorials) { 79 String featureName = VideoTutorialIPHUtils.getFeatureNameForNTP(tutorial.featureType); 80 if (featureName == null) continue; 81 if (mTracker.shouldTriggerHelpUI(featureName)) { 82 VideoTutorialMetrics.recordUserAction( 83 tutorial.featureType, UserAction.IPH_NTP_SHOWN); 84 mVideoIPHCoordinator.showVideoIPH(tutorial); 85 mTracker.dismissed(featureName); 86 break; 87 } 88 } 89 } 90 onClickIPH(Tutorial tutorial)91 private void onClickIPH(Tutorial tutorial) { 92 // TODO(shaktisahu): Maybe collect this event when video has been halfway watched. 93 mTracker.notifyEvent(VideoTutorialIPHUtils.getClickEvent(tutorial.featureType)); 94 VideoTutorialMetrics.recordUserAction(tutorial.featureType, UserAction.IPH_NTP_CLICKED); 95 96 // Bring up the player and start playing the video. 97 if (tutorial.featureType == FeatureType.SUMMARY) { 98 launchTutorialListActivity(); 99 } else { 100 launchVideoPlayer(tutorial); 101 } 102 } 103 onDismissIPH(Tutorial tutorial)104 private void onDismissIPH(Tutorial tutorial) { 105 mTracker.notifyEvent(VideoTutorialIPHUtils.getDismissEvent(tutorial.featureType)); 106 VideoTutorialMetrics.recordUserAction(tutorial.featureType, UserAction.IPH_NTP_DISMISSED); 107 108 // TODO(shaktisahu): Animate this. Maybe add a delay. 109 mVideoTutorialService.getTutorials(this::onFetchTutorials); 110 } 111 112 @VisibleForTesting launchVideoPlayer(Tutorial tutorial)113 protected void launchVideoPlayer(Tutorial tutorial) { 114 VideoPlayerActivity.playVideoTutorial(mContext, tutorial.featureType); 115 } 116 117 @VisibleForTesting launchTutorialListActivity()118 protected void launchTutorialListActivity() { 119 Intent intent = new Intent(); 120 intent.setClass(mContext, VideoTutorialListActivity.class); 121 mContext.startActivity(intent); 122 } 123 124 @VisibleForTesting createImageFetcher(Profile profile)125 protected ImageFetcher createImageFetcher(Profile profile) { 126 return ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE, 127 profile, GlobalDiscardableReferencePool.getReferencePool()); 128 } 129 130 @VisibleForTesting createVideoIPHCoordinator(ViewStub viewStub, ImageFetcher imageFetcher, Callback<Tutorial> clickListener, Callback<Tutorial> dismissListener)131 protected VideoIPHCoordinator createVideoIPHCoordinator(ViewStub viewStub, 132 ImageFetcher imageFetcher, Callback<Tutorial> clickListener, 133 Callback<Tutorial> dismissListener) { 134 return VideoTutorialServiceFactory.createVideoIPHCoordinator( 135 viewStub, imageFetcher, clickListener, dismissListener); 136 } 137 } 138