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