SetupUtils.java revision 1abddd9f6225298066094e20a6c29061b6af4590
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 17package com.android.tv.util; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.SharedPreferences; 23import android.media.tv.TvContract; 24import android.media.tv.TvInputInfo; 25import android.media.tv.TvInputManager; 26import android.os.Build; 27import android.preference.PreferenceManager; 28import android.util.Log; 29 30import com.android.tv.ApplicationSingletons; 31import com.android.tv.TvApplication; 32import com.android.tv.data.Channel; 33import com.android.tv.data.ChannelDataManager; 34 35import java.util.HashSet; 36import java.util.Set; 37 38/** 39 * A utility class related to input setup. 40 */ 41public class SetupUtils { 42 private static final String TAG = "SetupUtils"; 43 private static final boolean DEBUG = false; 44 45 // Known inputs are inputs which are shown in SetupView before. When a new input is installed, 46 // the input will not be included in "PREF_KEY_KNOWN_INPUTS". 47 private static final String PREF_KEY_KNOWN_INPUTS = "known_inputs"; 48 // Set up inputs are inputs whose setup activity has been launched and finished successfully. 49 private static final String PREF_KEY_SET_UP_INPUTS = "set_up_inputs"; 50 private static final String PREF_KEY_IS_FIRST_TUNE = "is_first_tune"; 51 private static SetupUtils sSetupUtils; 52 53 private final TvApplication mTvApplication; 54 private final SharedPreferences mSharedPreferences; 55 private final Set<String> mKnownInputs; 56 private final Set<String> mSetUpInputs; 57 private boolean mIsFirstTune; 58 private final String mUsbTunerInputId; 59 60 private SetupUtils(TvApplication tvApplication) { 61 mTvApplication = tvApplication; 62 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication); 63 mSetUpInputs = new HashSet<>(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, 64 new HashSet<String>())); 65 mKnownInputs = new HashSet<>(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS, 66 new HashSet<String>())); 67 mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true); 68 mUsbTunerInputId = TvContract.buildInputId(new ComponentName(tvApplication, 69 com.android.usbtuner.tvinput.UsbTunerTvInputService.class)); 70 } 71 72 /** 73 * Gets an instance of {@link SetupUtils}. 74 */ 75 public static SetupUtils getInstance(Context context) { 76 if (sSetupUtils != null) { 77 return sSetupUtils; 78 } 79 sSetupUtils = new SetupUtils((TvApplication) context.getApplicationContext()); 80 return sSetupUtils; 81 } 82 83 /** 84 * Additional work after the setup of TV input. 85 */ 86 public void onTvInputSetupFinished(final String inputId, final Runnable postRunnable) { 87 // When TIS adds several channels, ChannelDataManager.Listener.onChannelList 88 // Updated() can be called several times. In this case, it is hard to detect 89 // which one is the last callback. To reduce error prune, we update channel 90 // list again and make all channels of {@code inputId} browsable. 91 onSetupDone(inputId); 92 final ChannelDataManager manager = mTvApplication.getChannelDataManager(); 93 if (!manager.isDbLoadFinished()) { 94 manager.addListener(new ChannelDataManager.Listener() { 95 @Override 96 public void onLoadFinished() { 97 manager.removeListener(this); 98 updateChannelBrowsable(mTvApplication, inputId, postRunnable); 99 } 100 101 @Override 102 public void onChannelListUpdated() { } 103 104 @Override 105 public void onChannelBrowsableChanged() { } 106 }); 107 } else { 108 updateChannelBrowsable(mTvApplication, inputId, postRunnable); 109 } 110 } 111 112 private static void updateChannelBrowsable(Context context, final String inputId, 113 final Runnable postRunnable) { 114 ApplicationSingletons appSingletons = TvApplication.getSingletons(context); 115 final ChannelDataManager manager = appSingletons.getChannelDataManager(); 116 manager.updateChannels(new Runnable() { 117 @Override 118 public void run() { 119 boolean browsableChanged = false; 120 for (Channel channel : manager.getChannelList()) { 121 if (channel.getInputId().equals(inputId)) { 122 if (!channel.isBrowsable()) { 123 manager.updateBrowsable(channel.getId(), true, true); 124 browsableChanged = true; 125 } 126 } 127 } 128 if (browsableChanged) { 129 manager.notifyChannelBrowsableChanged(); 130 manager.applyUpdatedValuesToDb(); 131 } 132 if (postRunnable != null) { 133 postRunnable.run(); 134 } 135 } 136 }); 137 } 138 139 public boolean isFirstTune() { 140 return mIsFirstTune; 141 } 142 143 /** 144 * Returns true, if the input with {@code inputId} is newly installed. 145 */ 146 public boolean isNewInput(String inputId) { 147 return !mKnownInputs.contains(inputId); 148 } 149 150 /** 151 * Marks an input with {@code inputId} as a known input. Once it is marked, {@link #isNewInput} 152 * will return false. 153 */ 154 public void markAsKnownInput(String inputId) { 155 mKnownInputs.add(inputId); 156 mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs).apply(); 157 } 158 159 /** 160 * Returns {@code true}, if {@code inputId}'s setup has been done before. 161 */ 162 public boolean isSetupDone(String inputId) { 163 boolean done = mSetUpInputs.contains(inputId); 164 if (DEBUG) { 165 Log.d(TAG, "isSetupDone: (input=" + inputId + ", result= " + done + ")"); 166 } 167 return done; 168 } 169 170 /** 171 * Returns true, if there is any newly installed input. 172 */ 173 public boolean hasNewInput(TvInputManagerHelper inputManager) { 174 for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { 175 if (isNewInput(input.getId())) { 176 return true; 177 } 178 } 179 return false; 180 } 181 182 /** 183 * Grants permission for writing EPG data to all verified packages. 184 * 185 * @param context The Context used for granting permission. 186 */ 187 public static void grantEpgPermissionToSetUpPackages(Context context) { 188 // TvProvider allows granting of Uri permissions starting from MNC. 189 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 190 SharedPreferences sharedPreferences = 191 PreferenceManager.getDefaultSharedPreferences(context); 192 Set<String> setUpInputs = new HashSet<>(sharedPreferences.getStringSet( 193 PREF_KEY_SET_UP_INPUTS, new HashSet<String>())); 194 Set<String> setUpPackages = new HashSet<>(); 195 for (String input : setUpInputs) { 196 ComponentName componentName = null; 197 try { 198 componentName = ComponentName.unflattenFromString(input); 199 } catch (Exception e) { 200 Log.w(TAG, "Failed to unflatten string to component name (" + input + ")", e); 201 } 202 if (componentName == null) { 203 continue; 204 } 205 setUpPackages.add(componentName.getPackageName()); 206 } 207 for (String packageName : setUpPackages) { 208 grantEpgPermission(context, packageName); 209 } 210 } 211 } 212 213 /** 214 * Grants permission for writing EPG data to a given package. 215 * 216 * @param context The Context used for granting permission. 217 * @param packageName The name of the package to give permission. 218 */ 219 public static void grantEpgPermission(Context context, String packageName) { 220 // TvProvider allows granting of Uri permissions starting from MNC. 221 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 222 if (DEBUG) { 223 Log.d(TAG, "grantEpgPermission(context=" + context + ", packageName=" + packageName 224 + ")"); 225 } 226 try { 227 int modeFlags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION 228 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 229 context.grantUriPermission(packageName, TvContract.Channels.CONTENT_URI, modeFlags); 230 context.grantUriPermission(packageName, TvContract.Programs.CONTENT_URI, modeFlags); 231 } catch (SecurityException e) { 232 Log.e(TAG, "Either TvProvider does not allow granting of Uri permissions or the app" 233 + " does not have permission.", e); 234 } 235 } 236 } 237 238 /** 239 * Called when Live channels app is launched. Once it is called, {@link 240 * #isFirstTune} will return false. 241 */ 242 public void onTuned() { 243 if (!mIsFirstTune) { 244 return; 245 } 246 mIsFirstTune = false; 247 mSharedPreferences.edit().putBoolean(PREF_KEY_IS_FIRST_TUNE, false).apply(); 248 } 249 250 /** 251 * Called when input list is changed. It mainly handles input removals. 252 */ 253 public void onInputListUpdated(TvInputManager manager) { 254 // mKnownInputs is a super set of mSetUpInputs. 255 Set<String> removedInputList = new HashSet<>(mKnownInputs); 256 for (TvInputInfo input : manager.getTvInputList()) { 257 removedInputList.remove(input.getId()); 258 } 259 // A USB tuner device can be temporarily unplugged. We do not remove the USB tuner input 260 // from the known inputs so that the input won't appear as a new input whenever the user 261 // plugs in the USB tuner device again. 262 removedInputList.remove(mUsbTunerInputId); 263 264 if (!removedInputList.isEmpty()) { 265 for (String input : removedInputList) { 266 mSetUpInputs.remove(input); 267 mKnownInputs.remove(input); 268 } 269 mSharedPreferences.edit() 270 .putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply(); 271 mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs).apply(); 272 } 273 } 274 275 /** 276 * Called when an setup is done. Once it is called, {@link #isSetupDone} returns {@code true} 277 * for {@code inputId}. 278 */ 279 public void onSetupDone(String inputId) { 280 SoftPreconditions.checkState(inputId != null); 281 if (DEBUG) Log.d(TAG, "onSetupDone: input=" + inputId); 282 if (!mKnownInputs.contains(inputId)) { 283 Log.i(TAG, "An unknown input's setup has been done. inputId=" + inputId); 284 mKnownInputs.add(inputId); 285 } 286 mSetUpInputs.add(inputId); 287 mSharedPreferences.edit() 288 .putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply(); 289 } 290} 291