diff options
Diffstat (limited to 'src/com/android/tv/recommendation')
9 files changed, 531 insertions, 498 deletions
diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java index 2709ebe1..410b8252 100644 --- a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java +++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java @@ -28,16 +28,14 @@ import android.support.annotation.RequiresApi; import android.support.media.tv.TvContractCompat; import android.text.TextUtils; import android.util.Log; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; import com.android.tv.data.PreviewDataManager; import com.android.tv.data.PreviewProgramContent; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -48,23 +46,19 @@ import java.util.concurrent.TimeUnit; @RequiresApi(Build.VERSION_CODES.O) public class ChannelPreviewUpdater { private static final String TAG = "ChannelPreviewUpdater"; - // STOPSHIP: set it to false. - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int UPATE_PREVIEW_PROGRAMS_JOB_ID = 1000001; private static final long ROUTINE_INTERVAL_MS = TimeUnit.MINUTES.toMillis(10); // The left time of a program should meet the threshold so that it could be recommended. - private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = - TimeUnit.MINUTES.toMillis(10); - private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% + private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = TimeUnit.MINUTES.toMillis(10); + private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% private static final int RECOMMENDATION_COUNT = 6; private static final int MIN_COUNT_TO_ADD_ROW = 4; private static ChannelPreviewUpdater sChannelPreviewUpdater; - /** - * Creates and returns the {@link ChannelPreviewUpdater}. - */ + /** Creates and returns the {@link ChannelPreviewUpdater}. */ public static ChannelPreviewUpdater getInstance(Context context) { if (sChannelPreviewUpdater == null) { sChannelPreviewUpdater = new ChannelPreviewUpdater(context.getApplicationContext()); @@ -82,21 +76,22 @@ public class ChannelPreviewUpdater { private boolean mNeedUpdateAfterRecommenderReady = false; - private Recommender.Listener mRecommenderListener = new Recommender.Listener() { - @Override - public void onRecommenderReady() { - if (mNeedUpdateAfterRecommenderReady) { - if (DEBUG) Log.d(TAG, "Recommender is ready"); - updatePreviewDataForChannelsImmediately(); - mNeedUpdateAfterRecommenderReady = false; - } - } + private Recommender.Listener mRecommenderListener = + new Recommender.Listener() { + @Override + public void onRecommenderReady() { + if (mNeedUpdateAfterRecommenderReady) { + if (DEBUG) Log.d(TAG, "Recommender is ready"); + updatePreviewDataForChannelsImmediately(); + mNeedUpdateAfterRecommenderReady = false; + } + } - @Override - public void onRecommendationChanged() { - updatePreviewDataForChannelsImmediately(); - } - }; + @Override + public void onRecommendationChanged() { + updatePreviewDataForChannelsImmediately(); + } + }; private ChannelPreviewUpdater(Context context) { mContext = context; @@ -104,15 +99,13 @@ public class ChannelPreviewUpdater { mRecommender.registerEvaluator(new RandomEvaluator(), 0.1, 0.1); mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5); mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); - ApplicationSingletons appSingleton = TvApplication.getSingletons(context); - mPreviewDataManager = appSingleton.getPreviewDataManager(); - mParentalControlSettings = appSingleton.getTvInputManagerHelper() - .getParentalControlSettings(); + TvSingletons tvSingleton = TvSingletons.getSingletons(context); + mPreviewDataManager = tvSingleton.getPreviewDataManager(); + mParentalControlSettings = + tvSingleton.getTvInputManagerHelper().getParentalControlSettings(); } - /** - * Starts the routine service for updating the preview programs. - */ + /** Starts the routine service for updating the preview programs. */ public void startRoutineService() { JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -120,11 +113,13 @@ public class ChannelPreviewUpdater { if (DEBUG) Log.d(TAG, "UPDATE_PREVIEW_JOB already exists"); return; } - JobInfo job = new JobInfo.Builder(UPATE_PREVIEW_PROGRAMS_JOB_ID, - new ComponentName(mContext, ChannelPreviewUpdateService.class)) - .setPeriodic(ROUTINE_INTERVAL_MS) - .setPersisted(true) - .build(); + JobInfo job = + new JobInfo.Builder( + UPATE_PREVIEW_PROGRAMS_JOB_ID, + new ComponentName(mContext, ChannelPreviewUpdateService.class)) + .setPeriodic(ROUTINE_INTERVAL_MS) + .setPersisted(true) + .build(); if (jobScheduler.schedule(job) < 0) { Log.i(TAG, "JobScheduler failed to schedule the job"); } @@ -138,9 +133,7 @@ public class ChannelPreviewUpdater { updatePreviewDataForChannelsImmediately(); } - /** - * Updates the preview programs table. - */ + /** Updates the preview programs table. */ public void updatePreviewDataForChannelsImmediately() { if (!mRecommender.isReady()) { mNeedUpdateAfterRecommenderReady = true; @@ -148,16 +141,17 @@ public class ChannelPreviewUpdater { } if (!mPreviewDataManager.isLoadFinished()) { - mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() { - @Override - public void onPreviewDataLoadFinished() { - mPreviewDataManager.removeListener(this); - updatePreviewDataForChannels(); - } + mPreviewDataManager.addListener( + new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { + mPreviewDataManager.removeListener(this); + updatePreviewDataForChannels(); + } - @Override - public void onPreviewDataUpdateFinished() { } - }); + @Override + public void onPreviewDataUpdateFinished() {} + }); return; } updatePreviewDataForChannels(); @@ -225,8 +219,9 @@ public class ChannelPreviewUpdater { } private void updatePreviewDataForChannelsInternal(Set<Program> programs) { - long defaultPreviewChannelId = mPreviewDataManager.getPreviewChannelId( - PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL); + long defaultPreviewChannelId = + mPreviewDataManager.getPreviewChannelId( + PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL); if (defaultPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID) { // Only create if there is enough programs if (programs.size() > MIN_COUNT_TO_ADD_ROW) { @@ -248,7 +243,8 @@ public class ChannelPreviewUpdater { }); } } else { - updatePreviewProgramsForPreviewChannel(defaultPreviewChannelId, + updatePreviewProgramsForPreviewChannel( + defaultPreviewChannelId, generatePreviewProgramContentsFromPrograms(defaultPreviewChannelId, programs)); } } @@ -266,39 +262,38 @@ public class ChannelPreviewUpdater { return result; } - private void updatePreviewProgramsForPreviewChannel(long previewChannelId, - Set<PreviewProgramContent> previewProgramContents) { - PreviewDataManager.PreviewDataListener previewDataListener - = new PreviewDataManager.PreviewDataListener() { - @Override - public void onPreviewDataLoadFinished() { } - - @Override - public void onPreviewDataUpdateFinished() { - mPreviewDataManager.removeListener(this); - if (mJobService != null && mJobParams != null) { - if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService"); - mJobService.jobFinished(mJobParams, false); - mJobService = null; - mJobParams = null; - } else { - if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService"); - } - } - }; + private void updatePreviewProgramsForPreviewChannel( + long previewChannelId, Set<PreviewProgramContent> previewProgramContents) { + PreviewDataManager.PreviewDataListener previewDataListener = + new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() {} + + @Override + public void onPreviewDataUpdateFinished() { + mPreviewDataManager.removeListener(this); + if (mJobService != null && mJobParams != null) { + if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService"); + mJobService.jobFinished(mJobParams, false); + mJobService = null; + mJobParams = null; + } else { + if (DEBUG) + Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService"); + } + } + }; mPreviewDataManager.updatePreviewProgramsForChannel( previewChannelId, previewProgramContents, previewDataListener); } - /** - * Job to execute the update of preview programs. - */ + /** Job to execute the update of preview programs. */ public static class ChannelPreviewUpdateService extends JobService { private ChannelPreviewUpdater mChannelPreviewUpdater; @Override public void onCreate() { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); if (DEBUG) Log.d(TAG, "ChannelPreviewUpdateService.onCreate"); mChannelPreviewUpdater = ChannelPreviewUpdater.getInstance(this); } diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java index 26f0fbf0..c7a7cb37 100644 --- a/src/com/android/tv/recommendation/ChannelRecord.java +++ b/src/com/android/tv/recommendation/ChannelRecord.java @@ -17,14 +17,12 @@ package com.android.tv.recommendation; import android.content.Context; +import android.support.annotation.GuardedBy; import android.support.annotation.VisibleForTesting; - -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; +import com.android.tv.TvSingletons; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; -import com.android.tv.util.Utils; - +import com.android.tv.data.api.Channel; import java.util.ArrayDeque; import java.util.Deque; @@ -32,7 +30,10 @@ public class ChannelRecord { // TODO: decide the value for max history size. @VisibleForTesting static final int MAX_HISTORY_SIZE = 100; private final Context mContext; + + @GuardedBy("this") private final Deque<WatchedProgram> mWatchHistory; + private Program mCurrentProgram; private Channel mChannel; private long mTotalWatchDurationMs; @@ -62,7 +63,7 @@ public class ChannelRecord { mInputRemoved = removed; } - public long getLastWatchEndTimeMs() { + public synchronized long getLastWatchEndTimeMs() { WatchedProgram p = mWatchHistory.peekLast(); return (p == null) ? 0 : p.getWatchEndTimeMs(); } @@ -71,7 +72,7 @@ public class ChannelRecord { long time = System.currentTimeMillis(); if (mCurrentProgram == null || mCurrentProgram.getEndTimeUtcMillis() < time) { ProgramDataManager manager = - TvApplication.getSingletons(mContext).getProgramDataManager(); + TvSingletons.getSingletons(mContext).getProgramDataManager(); mCurrentProgram = manager.getCurrentProgram(mChannel.getId()); } return mCurrentProgram; @@ -81,11 +82,11 @@ public class ChannelRecord { return mTotalWatchDurationMs; } - public final WatchedProgram[] getWatchHistory() { + public final synchronized WatchedProgram[] getWatchHistory() { return mWatchHistory.toArray(new WatchedProgram[mWatchHistory.size()]); } - public void logWatchHistory(WatchedProgram p) { + public synchronized void logWatchHistory(WatchedProgram p) { mWatchHistory.offer(p); mTotalWatchDurationMs += p.getWatchedDurationMs(); if (mWatchHistory.size() > MAX_HISTORY_SIZE) { diff --git a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java index 9a6de7e2..8b0a3502 100644 --- a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java +++ b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java @@ -19,7 +19,7 @@ package com.android.tv.recommendation; import java.util.List; public class FavoriteChannelEvaluator extends Recommender.Evaluator { - private static final long MIN_WATCH_PERIOD_MS = 1000 * 60 * 60 * 24; // 1 day + private static final long MIN_WATCH_PERIOD_MS = 1000 * 60 * 60 * 24; // 1 day // When there is no watch history, use the current time as a default value. private long mEarliestWatchStartTimeMs = System.currentTimeMillis(); @@ -46,7 +46,6 @@ public class FavoriteChannelEvaluator extends Recommender.Evaluator { } long watchPeriodMs = System.currentTimeMillis() - mEarliestWatchStartTimeMs; - return (double) cr.getTotalWatchDurationMs() / - Math.max(watchPeriodMs, MIN_WATCH_PERIOD_MS); + return (double) cr.getTotalWatchDurationMs() / Math.max(watchPeriodMs, MIN_WATCH_PERIOD_MS); } } diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java index a44eca41..f40a0862 100644 --- a/src/com/android/tv/recommendation/NotificationService.java +++ b/src/com/android/tv/recommendation/NotificationService.java @@ -40,42 +40,39 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseLongArray; import android.view.View; - -import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivityWrapper.OnCurrentChannelChangeListener; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; +import com.android.tv.common.CommonConstants; import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; import com.android.tv.data.Program; -import com.android.tv.util.BitmapUtils; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; -import com.android.tv.util.ImageLoader; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - +import com.android.tv.util.images.BitmapUtils; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.util.images.ImageLoader; import java.util.ArrayList; import java.util.List; -/** - * A local service for notify recommendation at home launcher. - */ -public class NotificationService extends Service implements Recommender.Listener, - OnCurrentChannelChangeListener { +/** A local service for notify recommendation at home launcher. */ +public class NotificationService extends Service + implements Recommender.Listener, OnCurrentChannelChangeListener { private static final String TAG = "NotificationService"; private static final boolean DEBUG = false; public static final String ACTION_SHOW_RECOMMENDATION = - "com.android.tv.notification.ACTION_SHOW_RECOMMENDATION"; + CommonConstants.BASE_PACKAGE + ".notification.ACTION_SHOW_RECOMMENDATION"; public static final String ACTION_HIDE_RECOMMENDATION = - "com.android.tv.notification.ACTION_HIDE_RECOMMENDATION"; + CommonConstants.BASE_PACKAGE + ".notification.ACTION_HIDE_RECOMMENDATION"; /** - * Recommendation intent has an extra data for the recommendation type. It'll be also - * sent to a TV input as a tune parameter. + * Recommendation intent has an extra data for the recommendation type. It'll be also sent to a + * TV input as a tune parameter. */ public static final String TUNE_PARAMS_RECOMMENDATION_TYPE = - "com.android.tv.recommendation_type"; + CommonConstants.BASE_PACKAGE + ".recommendation_type"; private static final String TYPE_RANDOM_RECOMMENDATION = "random"; private static final String TYPE_ROUTINE_WATCH_RECOMMENDATION = "routine_watch"; @@ -92,9 +89,9 @@ public class NotificationService extends Service implements Recommender.Listener private static final int MSG_UPDATE_RECOMMENDATION = 1002; private static final int MSG_HIDE_RECOMMENDATION = 1003; - private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min - private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min - private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% + private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min + private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min + private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% private static final int MAX_PROGRAM_UPDATE_COUNT = 20; private TvInputManagerHelper mTvInputManagerHelper; @@ -126,17 +123,17 @@ public class NotificationService extends Service implements Recommender.Listener @Override public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate"); - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); super.onCreate(); mCurrentNotificationCount = 0; mNotificationChannels = new long[NOTIFICATION_COUNT]; for (int i = 0; i < NOTIFICATION_COUNT; ++i) { mNotificationChannels[i] = Channel.INVALID_ID; } - mNotificationCardMaxWidth = getResources().getDimensionPixelSize( - R.dimen.notif_card_img_max_width); - mNotificationCardHeight = getResources().getDimensionPixelSize( - R.dimen.notif_card_img_height); + mNotificationCardMaxWidth = + getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); + mNotificationCardHeight = + getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); mCardImageMaxWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); mCardImageMinWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_min_width); @@ -150,17 +147,17 @@ public class NotificationService extends Service implements Recommender.Listener getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_bottom); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - ApplicationSingletons appSingletons = TvApplication.getSingletons(this); - mTvInputManagerHelper = appSingletons.getTvInputManagerHelper(); + TvSingletons tvSingletons = TvSingletons.getSingletons(this); + mTvInputManagerHelper = tvSingletons.getTvInputManagerHelper(); mHandlerThread = new HandlerThread("tv notification"); mHandlerThread.start(); mHandler = new NotificationHandler(mHandlerThread.getLooper(), this); mHandler.sendEmptyMessage(MSG_INITIALIZE_RECOMMENDER); // Just called for early initialization. - appSingletons.getChannelDataManager(); - appSingletons.getProgramDataManager(); - appSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this); + tvSingletons.getChannelDataManager(); + tvSingletons.getProgramDataManager(); + tvSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this); } @UiThread @@ -178,8 +175,8 @@ public class NotificationService extends Service implements Recommender.Listener mRecommender.registerEvaluator(new RandomEvaluator()); } else if (TYPE_ROUTINE_WATCH_RECOMMENDATION.equals(mRecommendationType)) { mRecommender.registerEvaluator(new RoutineWatchEvaluator()); - } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION - .equals(mRecommendationType)) { + } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION.equals( + mRecommendationType)) { mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5); mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); } else { @@ -189,6 +186,9 @@ public class NotificationService extends Service implements Recommender.Listener } private void handleShowRecommendation() { + if (mRecommender == null) { + return; + } if (!mRecommender.isReady()) { mShowRecommendationAfterRecommenderReady = true; } else { @@ -197,13 +197,16 @@ public class NotificationService extends Service implements Recommender.Listener } private void handleUpdateRecommendation(int notificationId, Channel channel) { - if (mNotificationChannels[notificationId] == Channel.INVALID_ID || !sendNotification( - channel.getId(), notificationId)) { + if (mNotificationChannels[notificationId] == Channel.INVALID_ID + || !sendNotification(channel.getId(), notificationId)) { changeRecommendation(notificationId); } } private void handleHideRecommendation() { + if (mRecommender == null) { + return; + } if (!mRecommender.isReady()) { mShowRecommendationAfterRecommenderReady = false; } else { @@ -213,7 +216,8 @@ public class NotificationService extends Service implements Recommender.Listener @Override public void onDestroy() { - TvApplication.getSingletons(this).getMainActivityWrapper() + TvSingletons.getSingletons(this) + .getMainActivityWrapper() .removeOnCurrentChannelChangeListener(this); if (mRecommender != null) { mRecommender.release(); @@ -316,7 +320,7 @@ public class NotificationService extends Service implements Recommender.Listener } for (Channel c : channels) { if (!isNotifiedChannel(c.getId())) { - if(sendNotification(c.getId(), notificationId)) { + if (sendNotification(c.getId(), notificationId)) { return; } } @@ -334,13 +338,13 @@ public class NotificationService extends Service implements Recommender.Listener } private void hideAllRecommendation() { - for (int i = 0; i < NOTIFICATION_COUNT; ++i) { - if (mNotificationChannels[i] != Channel.INVALID_ID) { - mNotificationChannels[i] = Channel.INVALID_ID; - mNotificationManager.cancel(NOTIFY_TAG, i); - } - } - mCurrentNotificationCount = 0; + for (int i = 0; i < NOTIFICATION_COUNT; ++i) { + if (mNotificationChannels[i] != Channel.INVALID_ID) { + mNotificationChannels[i] = Channel.INVALID_ID; + mNotificationManager.cancel(NOTIFY_TAG, i); + } + } + mCurrentNotificationCount = 0; } private boolean sendNotification(final long channelId, final int notificationId) { @@ -350,8 +354,13 @@ public class NotificationService extends Service implements Recommender.Listener } final Channel channel = cr.getChannel(); if (DEBUG) { - Log.d(TAG, "sendNotification (channelName=" + channel.getDisplayName() + " notifyId=" - + notificationId + ")"); + Log.d( + TAG, + "sendNotification (channelName=" + + channel.getDisplayName() + + " notifyId=" + + notificationId + + ")"); } // TODO: Move some checking logic into TvRecommendation. @@ -363,17 +372,18 @@ public class NotificationService extends Service implements Recommender.Listener if (inputInfo == null) { return false; } - final String inputDisplayName = inputInfo.loadLabel(this).toString(); final Program program = Utils.getCurrentProgram(this, channel.getId()); if (program == null) { return false; } - final long programDurationMs = program.getEndTimeUtcMillis() - - program.getStartTimeUtcMillis(); + final long programDurationMs = + program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis(); long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); - final int programProgress = (programDurationMs <= 0) ? -1 - : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); + final int programProgress = + (programDurationMs <= 0) + ? -1 + : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); // We recommend those programs that meet the condition only. if (programProgress >= RECOMMENDATION_THRESHOLD_PROGRESS @@ -382,19 +392,24 @@ public class NotificationService extends Service implements Recommender.Listener } // We don't trust TIS to provide us with proper sized image - ScaledBitmapInfo posterArtBitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString(this, - program.getPosterArtUri(), (int) mNotificationCardMaxWidth, - (int) mNotificationCardHeight); + ScaledBitmapInfo posterArtBitmapInfo = + BitmapUtils.decodeSampledBitmapFromUriString( + this, + program.getPosterArtUri(), + (int) mNotificationCardMaxWidth, + (int) mNotificationCardHeight); if (posterArtBitmapInfo == null) { Log.e(TAG, "Failed to decode poster image for " + program.getPosterArtUri()); return false; } final Bitmap posterArtBitmap = posterArtBitmapInfo.bitmap; - channel.loadBitmap(this, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoMaxWidth, + channel.loadBitmap( + this, + Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, + mChannelLogoMaxWidth, mChannelLogoMaxHeight, - createChannelLogoCallback(this, notificationId, inputDisplayName, channel, program, - posterArtBitmap)); + createChannelLogoCallback(this, notificationId, channel, program, posterArtBitmap)); if (mNotificationChannels[notificationId] == Channel.INVALID_ID) { ++mCurrentNotificationCount; @@ -404,62 +419,77 @@ public class NotificationService extends Service implements Recommender.Listener return true; } - @NonNull - private static ImageLoader.ImageLoaderCallback<NotificationService> createChannelLogoCallback( - NotificationService service, final int notificationId, final String inputDisplayName, - final Channel channel, final Program program, final Bitmap posterArtBitmap) { - return new ImageLoader.ImageLoaderCallback<NotificationService>(service) { - @Override - public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) { - service.sendNotification(notificationId, channelLogo, channel, posterArtBitmap, - program, inputDisplayName); - } - }; - } - - private void sendNotification(int notificationId, Bitmap channelLogo, Channel channel, - Bitmap posterArtBitmap, Program program, String inputDisplayName) { - final long programDurationMs = program.getEndTimeUtcMillis() - program - .getStartTimeUtcMillis(); + private void sendNotification( + int notificationId, + Bitmap channelLogo, + Channel channel, + Bitmap posterArtBitmap, + Program program) { + final long programDurationMs = + program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis(); long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); - final int programProgress = (programDurationMs <= 0) ? -1 - : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); + final int programProgress = + (programDurationMs <= 0) + ? -1 + : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri()); intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0); // This callback will run on the main thread. - Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap - : overlayChannelLogo(channelLogo, posterArtBitmap); + Bitmap largeIconBitmap = + (channelLogo == null) + ? posterArtBitmap + : overlayChannelLogo(channelLogo, posterArtBitmap); String channelDisplayName = channel.getDisplayName(); - Notification notification = new Notification.Builder(this) - .setContentIntent(notificationIntent) - .setContentTitle(program.getTitle()) - .setContentText(TextUtils.isEmpty(channelDisplayName) ? channel.getDisplayNumber() - : channelDisplayName) - .setContentInfo(channelDisplayName) - .setAutoCancel(true).setLargeIcon(largeIconBitmap) - .setSmallIcon(R.drawable.ic_launcher_s) - .setCategory(Notification.CATEGORY_RECOMMENDATION) - .setProgress((programProgress > 0) ? 100 : 0, programProgress, false) - .setSortKey(mRecommender.getChannelSortKey(channel.getId())) - .build(); + Notification notification = + new Notification.Builder(this) + .setContentIntent(notificationIntent) + .setContentTitle(program.getTitle()) + .setContentText( + TextUtils.isEmpty(channelDisplayName) + ? channel.getDisplayNumber() + : channelDisplayName) + .setContentInfo(channelDisplayName) + .setAutoCancel(true) + .setLargeIcon(largeIconBitmap) + .setSmallIcon(R.drawable.ic_launcher_s) + .setCategory(Notification.CATEGORY_RECOMMENDATION) + .setProgress((programProgress > 0) ? 100 : 0, programProgress, false) + .setSortKey(mRecommender.getChannelSortKey(channel.getId())) + .build(); notification.color = getResources().getColor(R.color.recommendation_card_background, null); if (!TextUtils.isEmpty(program.getThumbnailUri())) { - notification.extras - .putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri()); + notification.extras.putString( + Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri()); } mNotificationManager.notify(NOTIFY_TAG, notificationId, notification); Message msg = mHandler.obtainMessage(MSG_UPDATE_RECOMMENDATION, notificationId, 0, channel); mHandler.sendMessageDelayed(msg, programDurationMs / MAX_PROGRAM_UPDATE_COUNT); } + @NonNull + private static ImageLoader.ImageLoaderCallback<NotificationService> createChannelLogoCallback( + NotificationService service, + final int notificationId, + final Channel channel, + final Program program, + final Bitmap posterArtBitmap) { + return new ImageLoader.ImageLoaderCallback<NotificationService>(service) { + @Override + public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) { + service.sendNotification( + notificationId, channelLogo, channel, posterArtBitmap, program); + } + }; + } + private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) { - Bitmap result = BitmapUtils.getScaledMutableBitmap( - background, Integer.MAX_VALUE, mCardImageHeight); - Bitmap scaledLogo = BitmapUtils.scaleBitmap( - logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight); + Bitmap result = + BitmapUtils.getScaledMutableBitmap(background, Integer.MAX_VALUE, mCardImageHeight); + Bitmap scaledLogo = + BitmapUtils.scaleBitmap(logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight); Canvas canvas; try { canvas = new Canvas(result); @@ -524,27 +554,32 @@ public class NotificationService extends Service implements Recommender.Listener @Override public void handleMessage(Message msg, @NonNull NotificationService notificationService) { switch (msg.what) { - case MSG_INITIALIZE_RECOMMENDER: { - notificationService.handleInitializeRecommender(); - break; - } - case MSG_SHOW_RECOMMENDATION: { - notificationService.handleShowRecommendation(); - break; - } - case MSG_UPDATE_RECOMMENDATION: { - int notificationId = msg.arg1; - Channel channel = ((Channel) msg.obj); - notificationService.handleUpdateRecommendation(notificationId, channel); - break; - } - case MSG_HIDE_RECOMMENDATION: { - notificationService.handleHideRecommendation(); - break; - } - default: { - super.handleMessage(msg); - } + case MSG_INITIALIZE_RECOMMENDER: + { + notificationService.handleInitializeRecommender(); + break; + } + case MSG_SHOW_RECOMMENDATION: + { + notificationService.handleShowRecommendation(); + break; + } + case MSG_UPDATE_RECOMMENDATION: + { + int notificationId = msg.arg1; + Channel channel = ((Channel) msg.obj); + notificationService.handleUpdateRecommendation(notificationId, channel); + break; + } + case MSG_HIDE_RECOMMENDATION: + { + notificationService.handleHideRecommendation(); + break; + } + default: + { + super.handleMessage(msg); + } } } } diff --git a/src/com/android/tv/recommendation/RecentChannelEvaluator.java b/src/com/android/tv/recommendation/RecentChannelEvaluator.java index e724f4ce..f4c4877d 100644 --- a/src/com/android/tv/recommendation/RecentChannelEvaluator.java +++ b/src/com/android/tv/recommendation/RecentChannelEvaluator.java @@ -51,9 +51,12 @@ public class RecentChannelEvaluator extends Recommender.Evaluator { if (watchDuration < WATCH_DURATION_MS_LOWER_BOUND) { watchDurationScore = MAX_SCORE_FOR_LOWER_BOUND; } else if (watchDuration < WATCH_DURATION_MS_UPPER_BOUND) { - watchDurationScore = (watchDuration - WATCH_DURATION_MS_LOWER_BOUND) - / (WATCH_DURATION_MS_UPPER_BOUND - WATCH_DURATION_MS_LOWER_BOUND) - * (1 - MAX_SCORE_FOR_LOWER_BOUND) + MAX_SCORE_FOR_LOWER_BOUND; + watchDurationScore = + (watchDuration - WATCH_DURATION_MS_LOWER_BOUND) + / (WATCH_DURATION_MS_UPPER_BOUND + - WATCH_DURATION_MS_LOWER_BOUND) + * (1 - MAX_SCORE_FOR_LOWER_BOUND) + + MAX_SCORE_FOR_LOWER_BOUND; } else { watchDurationScore = 1.0; } @@ -61,4 +64,4 @@ public class RecentChannelEvaluator extends Recommender.Evaluator { } return (maxScore > 0.0) ? maxScore : NOT_RECOMMENDED; } -}
\ No newline at end of file +} diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java index dc148ec8..649920fb 100644 --- a/src/com/android/tv/recommendation/RecommendationDataManager.java +++ b/src/com/android/tv/recommendation/RecommendationDataManager.java @@ -33,16 +33,14 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; - -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; +import com.android.tv.common.util.PermissionUtils; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.data.WatchedHistoryManager; -import com.android.tv.util.PermissionUtils; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvUriMatcher; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -52,6 +50,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +/** Manages teh data need to make recommendations. */ public class RecommendationDataManager implements WatchedHistoryManager.Listener { private static final int MSG_START = 1000; private static final int MSG_STOP = 1001; @@ -84,40 +83,38 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private final HandlerThread mHandlerThread; private final Handler mHandler; private final Handler mMainHandler; - @Nullable - private WatchedHistoryManager mWatchedHistoryManager; + @Nullable private WatchedHistoryManager mWatchedHistoryManager; private final ChannelDataManager mChannelDataManager; private final ChannelDataManager.Listener mChannelDataListener = new ChannelDataManager.Listener() { - @Override - @MainThread - public void onLoadFinished() { - updateChannelData(); - } + @Override + @MainThread + public void onLoadFinished() { + updateChannelData(); + } - @Override - @MainThread - public void onChannelListUpdated() { - updateChannelData(); - } + @Override + @MainThread + public void onChannelListUpdated() { + updateChannelData(); + } - @Override - @MainThread - public void onChannelBrowsableChanged() { - updateChannelData(); - } - }; + @Override + @MainThread + public void onChannelBrowsableChanged() { + updateChannelData(); + } + }; // For thread safety, this variable is handled only on main thread. private final List<Listener> mListeners = new ArrayList<>(); /** - * Gets instance of RecommendationDataManager, and adds a {@link Listener}. - * The listener methods will be called in the same thread as its caller of the method. - * Note that {@link #release(Listener)} should be called when this manager is not needed - * any more. + * Gets instance of RecommendationDataManager, and adds a {@link Listener}. The listener methods + * will be called in the same thread as its caller of the method. Note that {@link + * #release(Listener)} should be called when this manager is not needed any more. */ - public synchronized static RecommendationDataManager acquireManager( + public static synchronized RecommendationDataManager acquireManager( Context context, @NonNull Listener listener) { if (sManager == null) { sManager = new RecommendationDataManager(context); @@ -129,7 +126,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private final TvInputCallback mInternalCallback = new TvInputCallback() { @Override - public void onInputStateChanged(String inputId, int state) { } + public void onInputStateChanged(String inputId, int state) {} @Override public void onInputAdded(String inputId) { @@ -144,8 +141,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener for (ChannelRecord channelRecord : mChannelRecordMap.values()) { if (channelRecord.getChannel().getInputId().equals(inputId)) { channelRecord.setInputRemoved(false); - mAvailableChannelRecordMap.put(channelRecord.getChannel().getId(), - channelRecord); + mAvailableChannelRecordMap.put( + channelRecord.getChannel().getId(), channelRecord); channelRecordMapChanged = true; } } @@ -179,7 +176,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } @Override - public void onInputUpdated(String inputId) { } + public void onInputUpdated(String inputId) {} }; private RecommendationDataManager(Context context) { @@ -189,48 +186,44 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener mHandler = new RecommendationHandler(mHandlerThread.getLooper(), this); mMainHandler = new RecommendationMainHandler(Looper.getMainLooper(), this); mContentObserver = new RecommendationContentObserver(mHandler); - mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager(); - runOnMainThread(new Runnable() { - @Override - public void run() { - start(); - } - }); + mChannelDataManager = TvSingletons.getSingletons(mContext).getChannelDataManager(); + runOnMainThread( + new Runnable() { + @Override + public void run() { + start(); + } + }); } /** - * Removes the {@link Listener}, and releases RecommendationDataManager - * if there are no listeners remained. + * Removes the {@link Listener}, and releases RecommendationDataManager if there are no + * listeners remained. */ public void release(@NonNull final Listener listener) { - runOnMainThread(new Runnable() { - @Override - public void run() { - removeListener(listener); - if (mListeners.size() == 0) { - stop(); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + removeListener(listener); + if (mListeners.size() == 0) { + stop(); + } + } + }); } - /** - * Returns a {@link ChannelRecord} corresponds to the channel ID {@code ChannelId}. - */ + /** Returns a {@link ChannelRecord} corresponds to the channel ID {@code ChannelId}. */ public ChannelRecord getChannelRecord(long channelId) { return mAvailableChannelRecordMap.get(channelId); } - /** - * Returns the number of channels registered in ChannelRecord map. - */ + /** Returns the number of channels registered in ChannelRecord map. */ public int getChannelRecordCount() { return mAvailableChannelRecordMap.size(); } - /** - * Returns a Collection of ChannelRecords. - */ + /** Returns a Collection of ChannelRecords. */ public Collection<ChannelRecord> getChannelRecords() { return Collections.unmodifiableCollection(mAvailableChannelRecordMap.values()); } @@ -264,12 +257,13 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } private void addListener(Listener listener) { - runOnMainThread(new Runnable() { - @Override - public void run() { - mListeners.add(listener); - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + mListeners.add(listener); + } + }); } @MainThread @@ -286,10 +280,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener mWatchedHistoryManager.setListener(this); mWatchedHistoryManager.start(); } else { - mContext.getContentResolver().registerContentObserver( - TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver); - mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, - TvContract.WatchedPrograms.CONTENT_URI) + mContext.getContentResolver() + .registerContentObserver( + TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver); + mHandler.obtainMessage( + MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI) .sendToTarget(); } mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE); @@ -333,7 +328,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } } } - if (isChannelRecordMapChanged && mChannelRecordMapLoaded + if (isChannelRecordMapChanged + && mChannelRecordMapLoaded && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) { mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED); } @@ -356,14 +352,15 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram(watchedProgram); if (mChannelRecordMapLoaded && channelRecord != null) { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onNewWatchLog(channelRecord); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onNewWatchLog(channelRecord); + } + } + }); } } if (!mChannelRecordMapLoaded) { @@ -374,96 +371,99 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private WatchedProgram convertFromWatchedHistoryManagerRecords( WatchedHistoryManager.WatchedRecord watchedRecord) { long endTime = watchedRecord.watchedStartTime + watchedRecord.duration; - Program program = new Program.Builder() - .setChannelId(watchedRecord.channelId) - .setTitle("") - .setStartTimeUtcMillis(watchedRecord.watchedStartTime) - .setEndTimeUtcMillis(endTime) - .build(); + Program program = + new Program.Builder() + .setChannelId(watchedRecord.channelId) + .setTitle("") + .setStartTimeUtcMillis(watchedRecord.watchedStartTime) + .setEndTimeUtcMillis(endTime) + .build(); return new WatchedProgram(program, watchedRecord.watchedStartTime, endTime); } @Override public void onLoadFinished() { - for (WatchedHistoryManager.WatchedRecord record - : mWatchedHistoryManager.getWatchedHistory()) { - updateChannelRecordFromWatchedProgram( - convertFromWatchedHistoryManagerRecords(record)); + for (WatchedHistoryManager.WatchedRecord record : + mWatchedHistoryManager.getWatchedHistory()) { + updateChannelRecordFromWatchedProgram(convertFromWatchedHistoryManagerRecords(record)); } mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED); } @Override public void onNewRecordAdded(WatchedHistoryManager.WatchedRecord watchedRecord) { - final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram( - convertFromWatchedHistoryManagerRecords(watchedRecord)); + final ChannelRecord channelRecord = + updateChannelRecordFromWatchedProgram( + convertFromWatchedHistoryManagerRecords(watchedRecord)); if (mChannelRecordMapLoaded && channelRecord != null) { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onNewWatchLog(channelRecord); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onNewWatchLog(channelRecord); + } + } + }); } } private WatchedProgram createWatchedProgramFromWatchedProgramCursor(Cursor cursor) { // Have to initiate the indexes of WatchedProgram Columns. if (mIndexWatchChannelId == -1) { - mIndexWatchChannelId = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_CHANNEL_ID); - mIndexProgramTitle = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_TITLE); - mIndexProgramStartTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS); - mIndexProgramEndTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); - mIndexWatchStartTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); - mIndexWatchEndTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); + mIndexWatchChannelId = + cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID); + mIndexProgramTitle = cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_TITLE); + mIndexProgramStartTime = + cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS); + mIndexProgramEndTime = + cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); + mIndexWatchStartTime = + cursor.getColumnIndex( + TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); + mIndexWatchEndTime = + cursor.getColumnIndex( + TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); } - Program program = new Program.Builder() - .setChannelId(cursor.getLong(mIndexWatchChannelId)) - .setTitle(cursor.getString(mIndexProgramTitle)) - .setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime)) - .setEndTimeUtcMillis(cursor.getLong(mIndexProgramEndTime)) - .build(); + Program program = + new Program.Builder() + .setChannelId(cursor.getLong(mIndexWatchChannelId)) + .setTitle(cursor.getString(mIndexProgramTitle)) + .setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime)) + .setEndTimeUtcMillis(cursor.getLong(mIndexProgramEndTime)) + .build(); - return new WatchedProgram(program, - cursor.getLong(mIndexWatchStartTime), - cursor.getLong(mIndexWatchEndTime)); + return new WatchedProgram( + program, cursor.getLong(mIndexWatchStartTime), cursor.getLong(mIndexWatchEndTime)); } private void onNotifyChannelRecordMapLoaded() { mChannelRecordMapLoaded = true; - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onChannelRecordLoaded(); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onChannelRecordLoaded(); + } + } + }); } private void onNotifyChannelRecordMapChanged() { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onChannelRecordChanged(); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onChannelRecordChanged(); + } + } + }); } - /** - * Returns true if ChannelRecords are added into mChannelRecordMap or removed from it. - */ + /** Returns true if ChannelRecords are added into mChannelRecordMap or removed from it. */ private boolean updateChannelRecordMapFromChannel(Channel channel) { if (!channel.isBrowsable()) { mChannelRecordMap.remove(channel.getId()); @@ -507,8 +507,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener public void onChange(final boolean selfChange, final Uri uri) { switch (TvUriMatcher.match(uri)) { case TvUriMatcher.MATCH_WATCHED_PROGRAM_ID: - if (!mHandler.hasMessages(MSG_UPDATE_WATCH_HISTORY, - TvContract.WatchedPrograms.CONTENT_URI)) { + if (!mHandler.hasMessages( + MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)) { mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, uri).sendToTarget(); } break; @@ -524,15 +524,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } } - /** - * A listener interface to receive notification about the recommendation data. - * - * @MainThread - */ + /** A listener interface to receive notification about the recommendation data. @MainThread */ public interface Listener { /** - * Called when loading channel record map from database is finished. - * It will be called after RecommendationDataManager.start() is finished. + * Called when loading channel record map from database is finished. It will be called after + * RecommendationDataManager.start() is finished. * * <p>Note that this method is called on the main thread. */ @@ -601,6 +597,6 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } @Override - protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) { } + protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) {} } } diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java index 82c2893d..f350799f 100644 --- a/src/com/android/tv/recommendation/Recommender.java +++ b/src/com/android/tv/recommendation/Recommender.java @@ -20,9 +20,7 @@ import android.content.Context; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.util.Pair; - -import com.android.tv.data.Channel; - +import com.android.tv.data.api.Channel; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -35,8 +33,7 @@ import java.util.concurrent.TimeUnit; public class Recommender implements RecommendationDataManager.Listener { private static final String TAG = "Recommender"; - @VisibleForTesting - static final String INVALID_CHANNEL_SORT_KEY = "INVALID"; + @VisibleForTesting static final String INVALID_CHANNEL_SORT_KEY = "INVALID"; private static final long MINIMUM_RECOMMENDATION_UPDATE_PERIOD = TimeUnit.MINUTES.toMillis(5); private static final Comparator<Pair<Channel, Double>> mChannelScoreComparator = new Comparator<Pair<Channel, Double>>() { @@ -69,7 +66,9 @@ public class Recommender implements RecommendationDataManager.Listener { } @VisibleForTesting - Recommender(Listener listener, boolean includeRecommendedOnly, + Recommender( + Listener listener, + boolean includeRecommendedOnly, RecommendationDataManager dataManager) { mListener = listener; mIncludeRecommendedOnly = includeRecommendedOnly; @@ -85,16 +84,16 @@ public class Recommender implements RecommendationDataManager.Listener { } public void registerEvaluator(Evaluator evaluator) { - registerEvaluator(evaluator, - EvaluatorWrapper.DEFAULT_BASE_SCORE, EvaluatorWrapper.DEFAULT_WEIGHT); + registerEvaluator( + evaluator, EvaluatorWrapper.DEFAULT_BASE_SCORE, EvaluatorWrapper.DEFAULT_WEIGHT); } /** * Register the evaluator used in recommendation. * - * The range of evaluated scores by this evaluator will be between {@code baseScore} and + * <p>The range of evaluated scores by this evaluator will be between {@code baseScore} and * {@code baseScore} + {@code weight} (inclusive). - + * * @param evaluator The evaluator to register inside this recommender. * @param baseScore Base(Minimum) score of the score evaluated by {@code evaluator}. * @param weight Weight value to rearrange the score evaluated by {@code evaluator}. @@ -108,13 +107,13 @@ public class Recommender implements RecommendationDataManager.Listener { } /** - * Return the channel list of recommendation up to {@code n} or the number of channels. - * During the evaluation, this method updates the channel sort key of recommended channels. + * Return the channel list of recommendation up to {@code n} or the number of channels. During + * the evaluation, this method updates the channel sort key of recommended channels. * * @param size The number of channels that might be recommended. - * @return Top {@code size} channels recommended sorted by score in descending order. If - * {@code size} is bigger than the number of channels, the number of results could - * be less than {@code size}. + * @return Top {@code size} channels recommended sorted by score in descending order. If {@code + * size} is bigger than the number of channels, the number of results could be less than + * {@code size}. */ public List<Channel> recommendChannels(int size) { List<Pair<Channel, Double>> records = new ArrayList<>(); @@ -154,7 +153,7 @@ public class Recommender implements RecommendationDataManager.Listener { * * @param channelId The channel ID to retrieve the {@link Channel} object for. * @return the {@link Channel} object for the given channel ID, {@code null} if such a channel - * is not found. + * is not found. */ public Channel getChannel(long channelId) { ChannelRecord record = mDataManager.getChannelRecord(channelId); @@ -172,10 +171,10 @@ public class Recommender implements RecommendationDataManager.Listener { } /** - * Returns the sort key of a given channel Id. Sort key is determined in - * {@link #recommendChannels()} and getChannelSortKey must be called after that. + * Returns the sort key of a given channel Id. Sort key is determined in {@link + * #recommendChannels()} and getChannelSortKey must be called after that. * - * If getChannelSortKey was called before evaluating the channels or trying to get sort key + * <p>If getChannelSortKey was called before evaluating the channels or trying to get sort key * of non-recommended channel, it returns {@link #INVALID_CHANNEL_SORT_KEY}. */ public String getChannelSortKey(long channelId) { @@ -231,27 +230,25 @@ public class Recommender implements RecommendationDataManager.Listener { mLastRecommendationUpdatedTimeUtcMillis = newUpdatedTimeMs; } - public static abstract class Evaluator { + public abstract static class Evaluator { public static final double NOT_RECOMMENDED = -1.0; private Recommender mRecommender; protected Evaluator() {} - protected void onChannelRecordListChanged(List<ChannelRecord> channelRecords) { - } + protected void onChannelRecordListChanged(List<ChannelRecord> channelRecords) {} /** * This will be called when a new watch log comes into WatchedPrograms table. * * @param channelRecord The channel record corresponds to the new watch log. */ - protected void onNewWatchLog(ChannelRecord channelRecord) { - } + protected void onNewWatchLog(ChannelRecord channelRecord) {} /** - * The implementation should return the recommendation score for the given channel ID. - * The return value should be in the range of [0.0, 1.0] or NOT_RECOMMENDED for denoting - * that it gives up to calculate the score for the channel. + * The implementation should return the recommendation score for the given channel ID. The + * return value should be in the range of [0.0, 1.0] or NOT_RECOMMENDED for denoting that it + * gives up to calculate the score for the channel. * * @param channelId The channel ID which will be evaluated by this recommender. * @return The recommendation score @@ -278,8 +275,8 @@ public class Recommender implements RecommendationDataManager.Listener { // this value. private final double mWeight; - public EvaluatorWrapper(Recommender recommender, Evaluator evaluator, - double baseScore, double weight) { + public EvaluatorWrapper( + Recommender recommender, Evaluator evaluator, double baseScore, double weight) { mEvaluator = evaluator; evaluator.setRecommender(recommender); mBaseScore = baseScore; @@ -287,27 +284,27 @@ public class Recommender implements RecommendationDataManager.Listener { } /** - * This returns the scaled score for the given channel ID based on the returned value - * of evaluateChannel(). + * This returns the scaled score for the given channel ID based on the returned value of + * evaluateChannel(). * * @param channelId The channel ID which will be evaluated by the recommender. * @return Returns the scaled score (mBaseScore + score * mWeight) when evaluateChannel() is - * in the range of [0.0, 1.0]. If evaluateChannel() returns NOT_RECOMMENDED or any - * negative numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more - * than 1.0, it returns (mBaseScore + mWeight). + * in the range of [0.0, 1.0]. If evaluateChannel() returns NOT_RECOMMENDED or any + * negative numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more than + * 1.0, it returns (mBaseScore + mWeight). */ private double getScaledEvaluatorScore(long channelId) { double score = mEvaluator.evaluateChannel(channelId); if (score < 0.0) { if (score != Evaluator.NOT_RECOMMENDED) { - Log.w(TAG, "Unexpected score (" + score + ") from the recommender" - + mEvaluator); + Log.w( + TAG, + "Unexpected score (" + score + ") from the recommender" + mEvaluator); } // If the recommender gives up to calculate the score, return 0.0 return Evaluator.NOT_RECOMMENDED; } else if (score > 1.0) { - Log.w(TAG, "Unexpected score (" + score + ") from the recommender" - + mEvaluator); + Log.w(TAG, "Unexpected score (" + score + ") from the recommender" + mEvaluator); score = 1.0; } return mBaseScore + score * mWeight; @@ -323,14 +320,10 @@ public class Recommender implements RecommendationDataManager.Listener { } public interface Listener { - /** - * Called after channel record map is loaded. - */ + /** Called after channel record map is loaded. */ void onRecommenderReady(); - /** - * Called when the recommendation changes. - */ + /** Called when the recommendation changes. */ void onRecommendationChanged(); } } diff --git a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java index ad55afb7..edc23c53 100644 --- a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java +++ b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java @@ -21,39 +21,32 @@ import android.os.Build; import android.support.annotation.RequiresApi; import android.text.TextUtils; import android.util.Log; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.PreviewDataManager; import com.android.tv.data.PreviewProgramContent; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.data.RecordedProgram; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; -/** - * Class to update the preview data for {@link RecordedProgram} - */ +/** Class to update the preview data for {@link RecordedProgram} */ @RequiresApi(Build.VERSION_CODES.O) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class RecordedProgramPreviewUpdater { private static final String TAG = "RecordedProgramPreviewUpdater"; - // STOPSHIP: set it to false. - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int RECOMMENDATION_COUNT = 6; private static RecordedProgramPreviewUpdater sRecordedProgramPreviewUpdater; - /** - * Creates and returns the {@link RecordedProgramPreviewUpdater}. - */ + /** Creates and returns the {@link RecordedProgramPreviewUpdater}. */ public static RecordedProgramPreviewUpdater getInstance(Context context) { if (sRecordedProgramPreviewUpdater == null) { - sRecordedProgramPreviewUpdater - = new RecordedProgramPreviewUpdater(context.getApplicationContext()); + sRecordedProgramPreviewUpdater = + new RecordedProgramPreviewUpdater(context.getApplicationContext()); } return sRecordedProgramPreviewUpdater; } @@ -64,56 +57,56 @@ public class RecordedProgramPreviewUpdater { private RecordedProgramPreviewUpdater(Context context) { mContext = context.getApplicationContext(); - ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext); - mPreviewDataManager = applicationSingletons.getPreviewDataManager(); - mDvrDataManager = applicationSingletons.getDvrDataManager(); - mDvrDataManager.addRecordedProgramListener(new DvrDataManager.RecordedProgramListener() { - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { - if (DEBUG) Log.d(TAG, "Add new preview recorded programs"); - updatePreviewDataForRecordedPrograms(); - } + TvSingletons tvSingletons = TvSingletons.getSingletons(mContext); + mPreviewDataManager = tvSingletons.getPreviewDataManager(); + mDvrDataManager = tvSingletons.getDvrDataManager(); + mDvrDataManager.addRecordedProgramListener( + new DvrDataManager.RecordedProgramListener() { + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Add new preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { - if (DEBUG) Log.d(TAG, "Update preview recorded programs"); - updatePreviewDataForRecordedPrograms(); - } + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Update preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - if (DEBUG) Log.d(TAG, "Delete preview recorded programs"); - updatePreviewDataForRecordedPrograms(); - } - }); + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Delete preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } + }); } - /** - * Updates the preview data for recorded programs. - */ + /** Updates the preview data for recorded programs. */ public void updatePreviewDataForRecordedPrograms() { if (!mPreviewDataManager.isLoadFinished()) { - mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() { - @Override - public void onPreviewDataLoadFinished() { - mPreviewDataManager.removeListener(this); - updatePreviewDataForRecordedPrograms(); - } + mPreviewDataManager.addListener( + new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { + mPreviewDataManager.removeListener(this); + updatePreviewDataForRecordedPrograms(); + } - @Override - public void onPreviewDataUpdateFinished() { } - }); + @Override + public void onPreviewDataUpdateFinished() {} + }); return; } if (!mDvrDataManager.isRecordedProgramLoadFinished()) { mDvrDataManager.addRecordedProgramLoadFinishedListener( new DvrDataManager.OnRecordedProgramLoadFinishedListener() { - @Override - public void onRecordedProgramLoadFinished() { - mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); - updatePreviewDataForRecordedPrograms(); - } - }); + @Override + public void onRecordedProgramLoadFinished() { + mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); + updatePreviewDataForRecordedPrograms(); + } + }); return; } updatePreviewDataForRecordedProgramsInternal(); @@ -121,15 +114,18 @@ public class RecordedProgramPreviewUpdater { private void updatePreviewDataForRecordedProgramsInternal() { Set<RecordedProgram> recordedPrograms = generateRecommendationRecordedPrograms(); - Long recordedPreviewChannelId = mPreviewDataManager.getPreviewChannelId( - PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL); + Long recordedPreviewChannelId = + mPreviewDataManager.getPreviewChannelId( + PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL); if (recordedPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID && !recordedPrograms.isEmpty()) { createPreviewChannelForRecordedPrograms(); } else { - mPreviewDataManager.updatePreviewProgramsForChannel(recordedPreviewChannelId, + mPreviewDataManager.updatePreviewProgramsForChannel( + recordedPreviewChannelId, generatePreviewProgramContentsFromRecordedPrograms( - recordedPreviewChannelId, recordedPrograms), null); + recordedPreviewChannelId, recordedPrograms), + null); } } @@ -168,8 +164,9 @@ public class RecordedProgramPreviewUpdater { long previewChannelId, Set<RecordedProgram> recordedPrograms) { Set<PreviewProgramContent> result = new HashSet<>(); for (RecordedProgram recordedProgram : recordedPrograms) { - result.add(PreviewProgramContent.createFromRecordedProgram(mContext, previewChannelId, - recordedProgram)); + result.add( + PreviewProgramContent.createFromRecordedProgram( + mContext, previewChannelId, recordedProgram)); } return result; } diff --git a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java index 5ff7cae9..9240682a 100644 --- a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java +++ b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java @@ -19,9 +19,7 @@ package com.android.tv.recommendation; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; - import com.android.tv.data.Program; - import java.text.BreakIterator; import java.util.ArrayList; import java.util.Calendar; @@ -32,8 +30,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { // TODO: test and refine constant values in WatchedProgramRecommender in order to // improve the performance of this recommender. private static final double REQUIRED_MIN_SCORE = 0.15; - @VisibleForTesting - static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7; + @VisibleForTesting static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7; private static final double TITLE_MATCH_WEIGHT = 0.5; private static final double TIME_MATCH_WEIGHT = 1 - TITLE_MATCH_WEIGHT; private static final long DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(14); @@ -57,8 +54,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { } Program watchedProgram = watchHistory[watchHistory.length - 1].getProgram(); - long startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis() - - watchedProgram.getStartTimeUtcMillis(); + long startTimeDiffMsWithCurrentProgram = + currentProgram.getStartTimeUtcMillis() - watchedProgram.getStartTimeUtcMillis(); if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) { return NOT_RECOMMENDED; } @@ -70,42 +67,48 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { == watchHistory[i].getProgram().getStartTimeUtcMillis()) { watchedDurationMs += watchHistory[i].getWatchedDurationMs(); } else { - double score = calculateRoutineWatchScore( - currentProgram, watchedProgram, watchedDurationMs); + double score = + calculateRoutineWatchScore( + currentProgram, watchedProgram, watchedDurationMs); if (score >= REQUIRED_MIN_SCORE && score > maxScore) { maxScore = score; } watchedProgram = watchHistory[i].getProgram(); watchedDurationMs = watchHistory[i].getWatchedDurationMs(); - startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis() - - watchedProgram.getStartTimeUtcMillis(); + startTimeDiffMsWithCurrentProgram = + currentProgram.getStartTimeUtcMillis() + - watchedProgram.getStartTimeUtcMillis(); if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) { return maxScore; } } } - double score = calculateRoutineWatchScore( - currentProgram, watchedProgram, watchedDurationMs); + double score = + calculateRoutineWatchScore(currentProgram, watchedProgram, watchedDurationMs); if (score >= REQUIRED_MIN_SCORE && score > maxScore) { maxScore = score; } return maxScore; } - private static double calculateRoutineWatchScore(Program currentProgram, Program watchedProgram, - long watchedDurationMs) { + private static double calculateRoutineWatchScore( + Program currentProgram, Program watchedProgram, long watchedDurationMs) { double timeMatchScore = calculateTimeMatchScore(currentProgram, watchedProgram); - double titleMatchScore = calculateTitleMatchScore( - currentProgram.getTitle(), watchedProgram.getTitle()); + double titleMatchScore = + calculateTitleMatchScore(currentProgram.getTitle(), watchedProgram.getTitle()); double watchDurationScore = calculateWatchDurationScore(watchedProgram, watchedDurationMs); - long diffMs = currentProgram.getStartTimeUtcMillis() - - watchedProgram.getStartTimeUtcMillis(); - double multiplierForOldProgram = (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM) - ? 1.0 - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0) - / (MAX_DIFF_MS_FOR_OLD_PROGRAM - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM) - : 0.0; + long diffMs = + currentProgram.getStartTimeUtcMillis() - watchedProgram.getStartTimeUtcMillis(); + double multiplierForOldProgram = + (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM) + ? 1.0 + - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0) + / (MAX_DIFF_MS_FOR_OLD_PROGRAM + - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM) + : 0.0; return (titleMatchScore * TITLE_MATCH_WEIGHT + timeMatchScore * TIME_MATCH_WEIGHT) - * watchDurationScore * multiplierForOldProgram; + * watchDurationScore + * multiplierForOldProgram; } @VisibleForTesting @@ -118,8 +121,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { if (wordList1.isEmpty() || wordList2.isEmpty()) { return 0; } - int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength( - wordList1, wordList2); + int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength(wordList1, wordList2); // F-measure score double precision = (double) maxMatchedWordSeqLen / wordList1.size(); @@ -128,8 +130,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { } @VisibleForTesting - static int calculateMaximumMatchedWordSequenceLength(List<String> toSearchWords, - List<String> toMatchWords) { + static int calculateMaximumMatchedWordSequenceLength( + List<String> toSearchWords, List<String> toMatchWords) { int[] matchedWordSeqLen = new int[toMatchWords.size()]; int maxMatchedWordSeqLen = 0; for (String word : toSearchWords) { @@ -170,14 +172,20 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { boolean sameDay = false; // Handle cases like (00:00 - 02:00) - (01:00 - 03:00) or (22:00 - 25:00) - (23:00 - 26:00). - double score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec) - - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec)); + double score = + Math.max( + 0, + Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec) + - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec)); if (score > 0) { sameDay = (t1.weekDay == t2.weekDay); } else if (t1.dayChanged != t2.dayChanged) { // To handle cases like t1 : (00:00 - 01:00) and t2 : (23:00 - 25:00). - score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60) - - t1.startTimeOfDayInSec); + score = + Math.max( + 0, + Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60) + - t1.startTimeOfDayInSec); // Same day if next day of t2's start day equals to t1's start day. (1 <= weekDay <= 7) sameDay = (t1.weekDay == ((t2.weekDay % 7) + 1)); } @@ -206,7 +214,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { BreakIterator boundary = BreakIterator.getWordInstance(); boundary.setText(text); int start = boundary.first(); - for (int end = boundary.next(); end != BreakIterator.DONE; + for (int end = boundary.next(); + end != BreakIterator.DONE; start = end, end = boundary.next()) { String word = text.substring(start, end); if (Character.isLetterOrDigit(word.charAt(0))) { @@ -233,15 +242,20 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { time.setTimeInMillis(p.getEndTimeUtcMillis()); boolean dayChanged = (weekDay != time.get(Calendar.DAY_OF_WEEK)); // Set maximum program duration time to 12 hours. - int endTimeOfDayInSec = startTimeOfDayInSec + - (int) Math.min(p.getEndTimeUtcMillis() - p.getStartTimeUtcMillis(), - TimeUnit.HOURS.toMillis(12)) / 1000; + int endTimeOfDayInSec = + startTimeOfDayInSec + + (int) + Math.min( + p.getEndTimeUtcMillis() + - p.getStartTimeUtcMillis(), + TimeUnit.HOURS.toMillis(12)) + / 1000; return new ProgramTime(startTimeOfDayInSec, endTimeOfDayInSec, weekDay, dayChanged); } - private ProgramTime(int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay, - boolean dayChanged) { + private ProgramTime( + int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay, boolean dayChanged) { this.startTimeOfDayInSec = startTimeOfDayInSec; this.endTimeOfDayInSec = endTimeOfDayInSec; this.weekDay = weekDay; |