1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.menu;
18 
19 import android.content.Context;
20 import android.media.tv.TvTrackInfo;
21 import android.support.annotation.VisibleForTesting;
22 
23 import com.android.tv.Features;
24 import com.android.tv.R;
25 import com.android.tv.TvOptionsManager;
26 import com.android.tv.customization.CustomAction;
27 import com.android.tv.data.DisplayMode;
28 import com.android.tv.ui.TvViewUiManager;
29 import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
30 import com.android.tv.ui.sidepanel.DeveloperOptionFragment;
31 import com.android.tv.ui.sidepanel.DisplayModeFragment;
32 import com.android.tv.ui.sidepanel.MultiAudioFragment;
33 import com.android.tv.util.PipInputManager;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /*
39  * An adapter of options.
40  */
41 public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
42     private static final boolean ENABLE_IN_APP_PIP = false;
43 
44     private int mPositionPipAction;
45     // If mInAppPipAction is false, system-wide PIP is used.
46     private boolean mInAppPipAction = true;
47 
TvOptionsRowAdapter(Context context, List<CustomAction> customActions)48     public TvOptionsRowAdapter(Context context, List<CustomAction> customActions) {
49         super(context, customActions);
50     }
51 
52     @Override
createBaseActions()53     protected List<MenuAction> createBaseActions() {
54         List<MenuAction> actionList = new ArrayList<>();
55         actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
56         setOptionChangedListener(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
57         actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION);
58         setOptionChangedListener(MenuAction.SELECT_DISPLAY_MODE_ACTION);
59         actionList.add(MenuAction.PIP_IN_APP_ACTION);
60         setOptionChangedListener(MenuAction.PIP_IN_APP_ACTION);
61         mPositionPipAction = actionList.size() - 1;
62         actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
63         setOptionChangedListener(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
64         actionList.add(MenuAction.MORE_CHANNELS_ACTION);
65         if (DeveloperOptionFragment.shouldShow()) {
66             actionList.add(MenuAction.DEV_ACTION);
67         }
68         actionList.add(MenuAction.SETTINGS_ACTION);
69 
70         if (getCustomActions() != null) {
71             // Adjust Pip action position which will be changed by applying custom actions.
72             for (CustomAction customAction : getCustomActions()) {
73                 if (customAction.isFront()) {
74                     mPositionPipAction++;
75                 }
76             }
77         }
78 
79         return actionList;
80     }
81 
82     @Override
updateActions()83     protected boolean updateActions() {
84         boolean changed = false;
85         if (updatePipAction()) {
86             changed = true;
87         }
88         if (updateMultiAudioAction()) {
89             changed = true;
90         }
91         if (updateDisplayModeAction()) {
92             changed = true;
93         }
94         return changed;
95     }
96 
updatePipAction()97     private boolean updatePipAction() {
98         // There are four states.
99         // Case 1. The device doesn't even have any input for PIP. (e.g. OTT box without HDMI input)
100         //    => Remove the icon.
101         // Case 2. The device has one or more inputs for PIP but none of them are currently
102         // available.
103         //    => Show the icon but disable it.
104         // Case 3. The device has one or more available PIP inputs and now it's tuned off.
105         //    => Show the icon with "Off".
106         // Case 4. The device has one or more available PIP inputs but it's already turned on.
107         //    => Show the icon with "On".
108 
109         boolean changed = false;
110 
111         // Case 1
112         PipInputManager pipInputManager = getMainActivity().getPipInputManager();
113         if (ENABLE_IN_APP_PIP && pipInputManager.getPipInputSize(false) > 1) {
114             if (!mInAppPipAction) {
115                 removeAction(mPositionPipAction);
116                 addAction(mPositionPipAction, MenuAction.PIP_IN_APP_ACTION);
117                 mInAppPipAction = true;
118                 changed = true;
119             }
120         } else {
121             if (mInAppPipAction) {
122                 removeAction(mPositionPipAction);
123                 mInAppPipAction = false;
124                 if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) {
125                     addAction(mPositionPipAction, MenuAction.SYSTEMWIDE_PIP_ACTION);
126                 }
127                 return true;
128             }
129             return false;
130         }
131 
132         // Case 2
133         boolean isPipEnabled = getMainActivity().isPipEnabled();
134         boolean oldEnabled = MenuAction.PIP_IN_APP_ACTION.isEnabled();
135         boolean newEnabled = pipInputManager.getPipInputSize(true) > 0;
136         if (oldEnabled != newEnabled) {
137             // Should not disable the item if the PIP is already turned on so that the user can
138             // force exit it.
139             if (newEnabled || !isPipEnabled) {
140                 MenuAction.PIP_IN_APP_ACTION.setEnabled(newEnabled);
141                 changed = true;
142             }
143         }
144 
145         // Case 3 & 4 - we just need to update the icon.
146         MenuAction.PIP_IN_APP_ACTION.setDrawableResId(
147                 isPipEnabled ? R.drawable.ic_tvoption_pip : R.drawable.ic_tvoption_pip_off);
148         return changed;
149     }
150 
151     @VisibleForTesting
updateMultiAudioAction()152     boolean updateMultiAudioAction() {
153         List<TvTrackInfo> audioTracks = getMainActivity().getTracks(TvTrackInfo.TYPE_AUDIO);
154         boolean oldEnabled = MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled();
155         boolean newEnabled = audioTracks != null && audioTracks.size() > 1;
156         if (oldEnabled != newEnabled) {
157             MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.setEnabled(newEnabled);
158             return true;
159         }
160         return false;
161     }
162 
updateDisplayModeAction()163     private boolean updateDisplayModeAction() {
164         TvViewUiManager uiManager = getMainActivity().getTvViewUiManager();
165         boolean oldEnabled = MenuAction.SELECT_DISPLAY_MODE_ACTION.isEnabled();
166         boolean newEnabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL)
167                 || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM);
168         if (oldEnabled != newEnabled) {
169             MenuAction.SELECT_DISPLAY_MODE_ACTION.setEnabled(newEnabled);
170             return true;
171         }
172         return false;
173     }
174 
175     @Override
executeBaseAction(int type)176     protected void executeBaseAction(int type) {
177         switch (type) {
178             case TvOptionsManager.OPTION_CLOSED_CAPTIONS:
179                 getMainActivity().getOverlayManager().getSideFragmentManager()
180                         .show(new ClosedCaptionFragment());
181                 break;
182             case TvOptionsManager.OPTION_DISPLAY_MODE:
183                 getMainActivity().getOverlayManager().getSideFragmentManager()
184                         .show(new DisplayModeFragment());
185                 break;
186             case TvOptionsManager.OPTION_IN_APP_PIP:
187                 getMainActivity().togglePipView();
188                 break;
189             case TvOptionsManager.OPTION_SYSTEMWIDE_PIP:
190                 getMainActivity().enterPictureInPictureMode();
191                 break;
192             case TvOptionsManager.OPTION_MULTI_AUDIO:
193                 getMainActivity().getOverlayManager().getSideFragmentManager()
194                         .show(new MultiAudioFragment());
195                 break;
196             case TvOptionsManager.OPTION_MORE_CHANNELS:
197                 getMainActivity().showMerchantCollection();
198                 break;
199             case TvOptionsManager.OPTION_DEVELOPER:
200                 getMainActivity().getOverlayManager().getSideFragmentManager()
201                         .show(new DeveloperOptionFragment());
202                 break;
203             case TvOptionsManager.OPTION_SETTINGS:
204                 getMainActivity().showSettingsFragment();
205                 break;
206         }
207     }
208 }
209