1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/* 2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project 3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License. 6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at 7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software 11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and 14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License. 15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv.recommendation; 18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.app.Notification; 20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.app.NotificationManager; 21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.app.PendingIntent; 22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.app.Service; 23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context; 24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Intent; 25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.Bitmap; 26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.Canvas; 27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.Matrix; 28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.Paint; 29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.Rect; 30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvInputInfo; 31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Handler; 32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.HandlerThread; 33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.IBinder; 3407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport android.os.Looper; 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Message; 3607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport android.support.annotation.NonNull; 37ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.Nullable; 38ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.UiThread; 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.text.TextUtils; 40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log; 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.SparseLongArray; 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.view.View; 43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 441abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport com.android.tv.ApplicationSingletons; 45ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.MainActivityWrapper.OnCurrentChannelChangeListener; 46ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.R; 4707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.TvApplication; 4807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.common.WeakHandler; 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Channel; 50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Program; 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.BitmapUtils; 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.BitmapUtils.ScaledBitmapInfo; 53ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.util.ImageLoader; 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.TvInputManagerHelper; 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils; 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 57ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport java.util.ArrayList; 58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List; 59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/** 61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * A local service for notify recommendation at home launcher. 62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 63ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkopublic class NotificationService extends Service implements Recommender.Listener, 64ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko OnCurrentChannelChangeListener { 65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TAG = "NotificationService"; 66ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private static final boolean DEBUG = false; 67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final String ACTION_SHOW_RECOMMENDATION = 69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko "com.android.tv.notification.ACTION_SHOW_RECOMMENDATION"; 70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final String ACTION_HIDE_RECOMMENDATION = 71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko "com.android.tv.notification.ACTION_HIDE_RECOMMENDATION"; 72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 7307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko /** 7407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko * Recommendation intent has an extra data for the recommendation type. It'll be also 7507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko * sent to a TV input as a tune parameter. 7607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko */ 7707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko public static final String TUNE_PARAMS_RECOMMENDATION_TYPE = 78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko "com.android.tv.recommendation_type"; 7907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TYPE_RANDOM_RECOMMENDATION = "random"; 81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TYPE_ROUTINE_WATCH_RECOMMENDATION = "routine_watch"; 82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION = 83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko "routine_watch_and_favorite"; 84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String NOTIFY_TAG = "tv_recommendation"; 86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // TODO: find out proper number of notifications and whether to make it dynamically 87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // configurable from system property or etc. 88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int NOTIFICATION_COUNT = 3; 89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MSG_INITIALIZE_RECOMMENDER = 1000; 91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MSG_SHOW_RECOMMENDATION = 1001; 92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MSG_UPDATE_RECOMMENDATION = 1002; 93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MSG_HIDE_RECOMMENDATION = 1003; 94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min 96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min 97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% 98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int MAX_PROGRAM_UPDATE_COUNT = 20; 99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private TvInputManagerHelper mTvInputManagerHelper; 101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Recommender mRecommender; 102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean mShowRecommendationAfterRecommenderReady; 103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private NotificationManager mNotificationManager; 104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private HandlerThread mHandlerThread; 105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Handler mHandler; 106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final String mRecommendationType; 107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mCurrentNotificationCount; 108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private long[] mNotificationChannels; 109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 110ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private Channel mPlayingChannel; 111ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private float mNotificationCardMaxWidth; 113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private float mNotificationCardHeight; 114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mCardImageHeight; 115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mCardImageMaxWidth; 116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mCardImageMinWidth; 117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mChannelLogoMaxWidth; 118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mChannelLogoMaxHeight; 119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mLogoPaddingStart; 120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int mLogoPaddingBottom; 121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public NotificationService() { 123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommendationType = TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION; 124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onCreate() { 128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "onCreate"); 12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvApplication.setCurrentRunningProcess(this, true); 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko super.onCreate(); 131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentNotificationCount = 0; 132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationChannels = new long[NOTIFICATION_COUNT]; 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < NOTIFICATION_COUNT; ++i) { 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationChannels[i] = Channel.INVALID_ID; 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationCardMaxWidth = getResources().getDimensionPixelSize( 137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko R.dimen.notif_card_img_max_width); 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationCardHeight = getResources().getDimensionPixelSize( 139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko R.dimen.notif_card_img_height); 140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); 141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCardImageMaxWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); 142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCardImageMinWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_min_width); 143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelLogoMaxWidth = 144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_max_width); 145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelLogoMaxHeight = 146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_max_height); 147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLogoPaddingStart = 148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_start); 149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mLogoPaddingBottom = 150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_bottom); 151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 1531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko ApplicationSingletons appSingletons = TvApplication.getSingletons(this); 1541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko mTvInputManagerHelper = appSingletons.getTvInputManagerHelper(); 155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandlerThread = new HandlerThread("tv notification"); 156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandlerThread.start(); 15707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mHandler = new NotificationHandler(mHandlerThread.getLooper(), this); 158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.sendEmptyMessage(MSG_INITIALIZE_RECOMMENDER); 1597d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko 1607d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko // Just called for early initialization. 1611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko appSingletons.getChannelDataManager(); 1621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko appSingletons.getProgramDataManager(); 163ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko appSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this); 164ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 165ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 166ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @UiThread 167ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @Override 168ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public void onCurrentChannelChange(@Nullable Channel channel) { 169ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(TAG, "onCurrentChannelChange"); 170ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mPlayingChannel = channel; 171ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.removeMessages(MSG_SHOW_RECOMMENDATION); 172ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.sendEmptyMessage(MSG_SHOW_RECOMMENDATION); 173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private void handleInitializeRecommender() { 17607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mRecommender = new Recommender(NotificationService.this, NotificationService.this, true); 17707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko if (TYPE_RANDOM_RECOMMENDATION.equals(mRecommendationType)) { 17807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mRecommender.registerEvaluator(new RandomEvaluator()); 17907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } else if (TYPE_ROUTINE_WATCH_RECOMMENDATION.equals(mRecommendationType)) { 18007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mRecommender.registerEvaluator(new RoutineWatchEvaluator()); 18107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION 18207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko .equals(mRecommendationType)) { 18307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5); 18407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); 18507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } else { 18607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko throw new IllegalStateException( 18707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko "Undefined recommendation type: " + mRecommendationType); 18807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 18907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 19007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 19107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private void handleShowRecommendation() { 19207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko if (!mRecommender.isReady()) { 19307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mShowRecommendationAfterRecommenderReady = true; 19407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } else { 19507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko showRecommendation(); 19607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 19707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 19807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 19907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private void handleUpdateRecommendation(int notificationId, Channel channel) { 20007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko if (mNotificationChannels[notificationId] == Channel.INVALID_ID || !sendNotification( 20107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko channel.getId(), notificationId)) { 20207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko changeRecommendation(notificationId); 20307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 20407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 20507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 20607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private void handleHideRecommendation() { 20707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko if (!mRecommender.isReady()) { 20807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko mShowRecommendationAfterRecommenderReady = false; 20907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } else { 21007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko hideAllRecommendation(); 21107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 21207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 21307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onDestroy() { 216ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko TvApplication.getSingletons(this).getMainActivityWrapper() 217ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .removeOnCurrentChannelChangeListener(this); 218ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (mRecommender != null) { 219ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mRecommender.release(); 220ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mRecommender = null; 221ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 222ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (mHandlerThread != null) { 223ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandlerThread.quit(); 224ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandlerThread = null; 225ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler = null; 226ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko super.onDestroy(); 228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public int onStartCommand(Intent intent, int flags, int startId) { 232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "onStartCommand"); 233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (intent != null) { 234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko String action = intent.getAction(); 235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (ACTION_SHOW_RECOMMENDATION.equals(action)) { 236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.removeMessages(MSG_SHOW_RECOMMENDATION); 237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.removeMessages(MSG_HIDE_RECOMMENDATION); 238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.obtainMessage(MSG_SHOW_RECOMMENDATION).sendToTarget(); 239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else if (ACTION_HIDE_RECOMMENDATION.equals(action)) { 240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.removeMessages(MSG_SHOW_RECOMMENDATION); 241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.removeMessages(MSG_UPDATE_RECOMMENDATION); 242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.removeMessages(MSG_HIDE_RECOMMENDATION); 243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.obtainMessage(MSG_HIDE_RECOMMENDATION).sendToTarget(); 244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return START_STICKY; 247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public IBinder onBind(Intent intent) { 251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onRecommenderReady() { 256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "onRecommendationReady"); 257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mShowRecommendationAfterRecommenderReady) { 258ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.removeMessages(MSG_SHOW_RECOMMENDATION); 259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mHandler.sendEmptyMessage(MSG_SHOW_RECOMMENDATION); 260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mShowRecommendationAfterRecommenderReady = false; 261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onRecommendationChanged() { 266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "onRecommendationChanged"); 267ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko // Update recommendation on the handler thread. 268ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.removeMessages(MSG_SHOW_RECOMMENDATION); 269ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.sendEmptyMessage(MSG_SHOW_RECOMMENDATION); 270ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 271ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 272ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private void showRecommendation() { 273ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(TAG, "showRecommendation"); 274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko SparseLongArray notificationChannels = new SparseLongArray(); 275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < NOTIFICATION_COUNT; ++i) { 276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationChannels[i] == Channel.INVALID_ID) { 277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko continue; 278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko notificationChannels.put(i, mNotificationChannels[i]); 280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 281ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko List<Channel> channels = recommendChannels(); 282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (Channel c : channels) { 283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int index = notificationChannels.indexOfValue(c.getId()); 284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (index >= 0) { 285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko notificationChannels.removeAt(index); 286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Cancel notification whose channels are not recommended anymore. 289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (notificationChannels.size() > 0) { 290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < notificationChannels.size(); ++i) { 291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int notificationId = notificationChannels.keyAt(i); 292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationManager.cancel(NOTIFY_TAG, notificationId); 293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationChannels[notificationId] = Channel.INVALID_ID; 294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko --mCurrentNotificationCount; 295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (Channel c : channels) { 298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mCurrentNotificationCount >= NOTIFICATION_COUNT) { 299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko break; 300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isNotifiedChannel(c.getId())) { 302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sendNotification(c.getId(), getAvailableNotificationId()); 303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mCurrentNotificationCount < NOTIFICATION_COUNT) { 306ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.sendEmptyMessageDelayed(MSG_SHOW_RECOMMENDATION, RECOMMENDATION_RETRY_TIME_MS); 307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void changeRecommendation(int notificationId) { 311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) Log.d(TAG, "changeRecommendation"); 312ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko List<Channel> channels = recommendChannels(); 313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationChannels[notificationId] != Channel.INVALID_ID) { 314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationChannels[notificationId] = Channel.INVALID_ID; 315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko --mCurrentNotificationCount; 316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (Channel c : channels) { 318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!isNotifiedChannel(c.getId())) { 319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if(sendNotification(c.getId(), notificationId)) { 320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return; 321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationManager.cancel(NOTIFY_TAG, notificationId); 325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 327ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private List<Channel> recommendChannels() { 328ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko List channels = mRecommender.recommendChannels(); 329ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (channels.contains(mPlayingChannel)) { 330ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko channels = new ArrayList<>(channels); 331ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko channels.remove(mPlayingChannel); 332ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 333ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return channels; 334ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 335ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void hideAllRecommendation() { 337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < NOTIFICATION_COUNT; ++i) { 338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationChannels[i] != Channel.INVALID_ID) { 339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationChannels[i] = Channel.INVALID_ID; 340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationManager.cancel(NOTIFY_TAG, i); 341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mCurrentNotificationCount = 0; 344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean sendNotification(final long channelId, final int notificationId) { 347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final ChannelRecord cr = mRecommender.getChannelRecord(channelId); 348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (cr == null) { 349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final Channel channel = cr.getChannel(); 3527d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko if (DEBUG) { 3537d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko Log.d(TAG, "sendNotification (channelName=" + channel.getDisplayName() + " notifyId=" 3547d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko + notificationId + ")"); 3557d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko } 356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // TODO: Move some checking logic into TvRecommendation. 358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko String inputId = Utils.getInputIdForChannel(this, channel.getId()); 359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (TextUtils.isEmpty(inputId)) { 360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(inputId); 363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (inputInfo == null) { 364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final String inputDisplayName = inputInfo.loadLabel(this).toString(); 367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final Program program = Utils.getCurrentProgram(this, channel.getId()); 369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (program == null) { 370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final long programDurationMs = program.getEndTimeUtcMillis() 373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - program.getStartTimeUtcMillis(); 374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); 375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final int programProgress = (programDurationMs <= 0) ? -1 376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); 377816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // We recommend those programs that meet the condition only. 379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (programProgress >= RECOMMENDATION_THRESHOLD_PROGRESS 380816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko && programLeftTimsMs <= RECOMMENDATION_THRESHOLD_LEFT_TIME_MS) { 381816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 382816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // We don't trust TIS to provide us with proper sized image 385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo posterArtBitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString(this, 386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko program.getPosterArtUri(), (int) mNotificationCardMaxWidth, 387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko (int) mNotificationCardHeight); 388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (posterArtBitmapInfo == null) { 389816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.e(TAG, "Failed to decode poster image for " + program.getPosterArtUri()); 390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 391816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 392816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final Bitmap posterArtBitmap = posterArtBitmapInfo.bitmap; 393816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 394816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko channel.loadBitmap(this, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoMaxWidth, 395ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mChannelLogoMaxHeight, 396ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko createChannelLogoCallback(this, notificationId, inputDisplayName, channel, program, 397ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko posterArtBitmap)); 398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationChannels[notificationId] == Channel.INVALID_ID) { 400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ++mCurrentNotificationCount; 401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mNotificationChannels[notificationId] = channel.getId(); 403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 407ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @NonNull 408ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private static ImageLoader.ImageLoaderCallback<NotificationService> createChannelLogoCallback( 409ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko NotificationService service, final int notificationId, final String inputDisplayName, 410ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko final Channel channel, final Program program, final Bitmap posterArtBitmap) { 411ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return new ImageLoader.ImageLoaderCallback<NotificationService>(service) { 412ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @Override 413ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) { 414ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko service.sendNotification(notificationId, channelLogo, channel, posterArtBitmap, 415ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko program, inputDisplayName); 416ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 417ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko }; 418ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 419ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 420ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private void sendNotification(int notificationId, Bitmap channelLogo, Channel channel, 42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Bitmap posterArtBitmap, Program program, String inputDisplayName) { 422ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko final long programDurationMs = program.getEndTimeUtcMillis() - program 423ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .getStartTimeUtcMillis(); 424ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); 425ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko final int programProgress = (programDurationMs <= 0) ? -1 426ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); 427ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri()); 428ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType); 4296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 430ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0); 431ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 432ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko // This callback will run on the main thread. 433ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap 434ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko : overlayChannelLogo(channelLogo, posterArtBitmap); 435ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko String channelDisplayName = channel.getDisplayName(); 436ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Notification notification = new Notification.Builder(this) 43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setContentIntent(notificationIntent) 43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setContentTitle(program.getTitle()) 43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setContentText(TextUtils.isEmpty(channelDisplayName) ? channel.getDisplayNumber() 44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko : channelDisplayName) 44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setContentInfo(channelDisplayName) 442ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setAutoCancel(true).setLargeIcon(largeIconBitmap) 443ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setSmallIcon(R.drawable.ic_launcher_s) 444ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setCategory(Notification.CATEGORY_RECOMMENDATION) 445ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setProgress((programProgress > 0) ? 100 : 0, programProgress, false) 44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setSortKey(mRecommender.getChannelSortKey(channel.getId())) 44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .build(); 44865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notification.color = getResources().getColor(R.color.recommendation_card_background, null); 449ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (!TextUtils.isEmpty(program.getThumbnailUri())) { 450ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko notification.extras 451ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri()); 452ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 453ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mNotificationManager.notify(NOTIFY_TAG, notificationId, notification); 454ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Message msg = mHandler.obtainMessage(MSG_UPDATE_RECOMMENDATION, notificationId, 0, channel); 455ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mHandler.sendMessageDelayed(msg, programDurationMs / MAX_PROGRAM_UPDATE_COUNT); 456ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 457ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 458816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) { 4596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko Bitmap result = BitmapUtils.getScaledMutableBitmap( 460816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko background, Integer.MAX_VALUE, mCardImageHeight); 461816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Bitmap scaledLogo = BitmapUtils.scaleBitmap( 462816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight); 4636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko Canvas canvas; 4646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko try { 4656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko canvas = new Canvas(result); 4666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } catch (Exception e) { 4676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko Log.w(TAG, "Failed to create Canvas", e); 4686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko return background; 4696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko } 470816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko canvas.drawBitmap(result, new Matrix(), null); 471816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Rect rect = new Rect(); 472816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int startPadding; 473816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (result.getWidth() < mCardImageMinWidth) { 474816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // TODO: check the positions. 475816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startPadding = mLogoPaddingStart; 476816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.bottom = result.getHeight() - mLogoPaddingBottom; 477816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.top = rect.bottom - scaledLogo.getHeight(); 478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else if (result.getWidth() < mCardImageMaxWidth) { 479816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startPadding = mLogoPaddingStart; 480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.bottom = result.getHeight() - mLogoPaddingBottom; 481816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.top = rect.bottom - scaledLogo.getHeight(); 482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int marginStart = (result.getWidth() - mCardImageMaxWidth) / 2; 484816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startPadding = mLogoPaddingStart + marginStart; 485816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.bottom = result.getHeight() - mLogoPaddingBottom; 486816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.top = rect.bottom - scaledLogo.getHeight(); 487816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 488816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) { 489816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.left = startPadding; 490816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.right = startPadding + scaledLogo.getWidth(); 491816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.right = result.getWidth() - startPadding; 493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko rect.left = rect.right - scaledLogo.getWidth(); 494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Paint paint = new Paint(); 496816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko paint.setAlpha(getResources().getInteger(R.integer.notif_card_ch_logo_alpha)); 497816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko canvas.drawBitmap(scaledLogo, null, rect, paint); 498816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return result; 499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean isNotifiedChannel(long channelId) { 502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < NOTIFICATION_COUNT; ++i) { 503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationChannels[i] == channelId) { 504816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 505816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 506816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 507816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 508816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 509816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 510816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private int getAvailableNotificationId() { 511816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < NOTIFICATION_COUNT; ++i) { 512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mNotificationChannels[i] == Channel.INVALID_ID) { 513816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return i; 514816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 515816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 516816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return -1; 517816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 51807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 51907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private static class NotificationHandler extends WeakHandler<NotificationService> { 52007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko public NotificationHandler(@NonNull Looper looper, NotificationService ref) { 52107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko super(looper, ref); 52207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 52307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko 52407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko @Override 52507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko public void handleMessage(Message msg, @NonNull NotificationService notificationService) { 52607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko switch (msg.what) { 52707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko case MSG_INITIALIZE_RECOMMENDER: { 52807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko notificationService.handleInitializeRecommender(); 52907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko break; 53007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 53107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko case MSG_SHOW_RECOMMENDATION: { 53207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko notificationService.handleShowRecommendation(); 53307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko break; 53407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 53507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko case MSG_UPDATE_RECOMMENDATION: { 53607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko int notificationId = msg.arg1; 53707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko Channel channel = ((Channel) msg.obj); 53807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko notificationService.handleUpdateRecommendation(notificationId, channel); 53907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko break; 54007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 54107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko case MSG_HIDE_RECOMMENDATION: { 54207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko notificationService.handleHideRecommendation(); 54307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko break; 54407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 54507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko default: { 54607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko super.handleMessage(msg); 54707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 54807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 54907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 55007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko } 551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 552