| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tv.util; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.media.tv.TvContract; |
| import android.media.tv.TvInputInfo; |
| import android.media.tv.TvInputManager; |
| import android.os.Build; |
| import android.preference.PreferenceManager; |
| import android.support.annotation.Nullable; |
| import android.support.annotation.UiThread; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.Log; |
| |
| import com.android.tv.ApplicationSingletons; |
| import com.android.tv.TvApplication; |
| import com.android.tv.common.SoftPreconditions; |
| import com.android.tv.data.Channel; |
| import com.android.tv.data.ChannelDataManager; |
| |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * A utility class related to input setup. |
| */ |
| public class SetupUtils { |
| private static final String TAG = "SetupUtils"; |
| private static final boolean DEBUG = false; |
| |
| // Known inputs are inputs which are shown in SetupView before. When a new input is installed, |
| // the input will not be included in "PREF_KEY_KNOWN_INPUTS". |
| private static final String PREF_KEY_KNOWN_INPUTS = "known_inputs"; |
| // Set up inputs are inputs whose setup activity has been launched and finished successfully. |
| private static final String PREF_KEY_SET_UP_INPUTS = "set_up_inputs"; |
| // Recognized inputs means that the user already knows the inputs are installed. |
| private static final String PREF_KEY_RECOGNIZED_INPUTS = "recognized_inputs"; |
| private static final String PREF_KEY_IS_FIRST_TUNE = "is_first_tune"; |
| private static SetupUtils sSetupUtils; |
| |
| private final TvApplication mTvApplication; |
| private final SharedPreferences mSharedPreferences; |
| private final Set<String> mKnownInputs; |
| private final Set<String> mSetUpInputs; |
| private final Set<String> mRecognizedInputs; |
| private boolean mIsFirstTune; |
| private final String mUsbTunerInputId; |
| |
| private SetupUtils(TvApplication tvApplication) { |
| mTvApplication = tvApplication; |
| mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication); |
| mSetUpInputs = new ArraySet<>(); |
| mSetUpInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, |
| Collections.<String>emptySet())); |
| mKnownInputs = new ArraySet<>(); |
| mKnownInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS, |
| Collections.<String>emptySet())); |
| mRecognizedInputs = new ArraySet<>(); |
| mRecognizedInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS, |
| mKnownInputs)); |
| mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true); |
| mUsbTunerInputId = TvContract.buildInputId(new ComponentName(tvApplication, |
| com.android.usbtuner.tvinput.UsbTunerTvInputService.class)); |
| } |
| |
| /** |
| * Gets an instance of {@link SetupUtils}. |
| */ |
| public static SetupUtils getInstance(Context context) { |
| if (sSetupUtils != null) { |
| return sSetupUtils; |
| } |
| sSetupUtils = new SetupUtils((TvApplication) context.getApplicationContext()); |
| return sSetupUtils; |
| } |
| |
| /** |
| * Additional work after the setup of TV input. |
| */ |
| public void onTvInputSetupFinished(final String inputId, |
| @Nullable final Runnable postRunnable) { |
| // When TIS adds several channels, ChannelDataManager.Listener.onChannelList |
| // Updated() can be called several times. In this case, it is hard to detect |
| // which one is the last callback. To reduce error prune, we update channel |
| // list again and make all channels of {@code inputId} browsable. |
| onSetupDone(inputId); |
| final ChannelDataManager manager = mTvApplication.getChannelDataManager(); |
| if (!manager.isDbLoadFinished()) { |
| manager.addListener(new ChannelDataManager.Listener() { |
| @Override |
| public void onLoadFinished() { |
| manager.removeListener(this); |
| updateChannelBrowsable(mTvApplication, inputId, postRunnable); |
| } |
| |
| @Override |
| public void onChannelListUpdated() { } |
| |
| @Override |
| public void onChannelBrowsableChanged() { } |
| }); |
| } else { |
| updateChannelBrowsable(mTvApplication, inputId, postRunnable); |
| } |
| } |
| |
| private static void updateChannelBrowsable(Context context, final String inputId, |
| final Runnable postRunnable) { |
| ApplicationSingletons appSingletons = TvApplication.getSingletons(context); |
| final ChannelDataManager manager = appSingletons.getChannelDataManager(); |
| manager.updateChannels(new Runnable() { |
| @Override |
| public void run() { |
| boolean browsableChanged = false; |
| for (Channel channel : manager.getChannelList()) { |
| if (channel.getInputId().equals(inputId)) { |
| if (!channel.isBrowsable()) { |
| manager.updateBrowsable(channel.getId(), true, true); |
| browsableChanged = true; |
| } |
| } |
| } |
| if (browsableChanged) { |
| manager.notifyChannelBrowsableChanged(); |
| manager.applyUpdatedValuesToDb(); |
| } |
| if (postRunnable != null) { |
| postRunnable.run(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Marks the channels in newly installed inputs browsable. |
| */ |
| @UiThread |
| public void markNewChannelsBrowsable() { |
| Set<String> newInputsWithChannels = new HashSet<>(); |
| TvInputManagerHelper tvInputManagerHelper = mTvApplication.getTvInputManagerHelper(); |
| ChannelDataManager channelDataManager = mTvApplication.getChannelDataManager(); |
| SoftPreconditions.checkState(channelDataManager.isDbLoadFinished()); |
| for (TvInputInfo input : tvInputManagerHelper.getTvInputInfos(true, true)) { |
| String inputId = input.getId(); |
| if (!isSetupDone(inputId) && channelDataManager.getChannelCountForInput(inputId) > 0) { |
| onSetupDone(inputId); |
| newInputsWithChannels.add(inputId); |
| if (DEBUG) { |
| Log.d(TAG, "New input " + inputId + " has " |
| + channelDataManager.getChannelCountForInput(inputId) |
| + " channels"); |
| } |
| } |
| } |
| if (!newInputsWithChannels.isEmpty()) { |
| for (Channel channel : channelDataManager.getChannelList()) { |
| if (newInputsWithChannels.contains(channel.getInputId())) { |
| channelDataManager.updateBrowsable(channel.getId(), true); |
| } |
| } |
| channelDataManager.applyUpdatedValuesToDb(); |
| } |
| } |
| |
| public boolean isFirstTune() { |
| return mIsFirstTune; |
| } |
| |
| /** |
| * Returns true, if the input with {@code inputId} is newly installed. |
| */ |
| public boolean isNewInput(String inputId) { |
| return !mKnownInputs.contains(inputId); |
| } |
| |
| /** |
| * Marks an input with {@code inputId} as a known input. Once it is marked, {@link #isNewInput} |
| * will return false. |
| */ |
| public void markAsKnownInput(String inputId) { |
| mKnownInputs.add(inputId); |
| mRecognizedInputs.add(inputId); |
| mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs) |
| .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply(); |
| } |
| |
| /** |
| * Returns {@code true}, if {@code inputId}'s setup has been done before. |
| */ |
| public boolean isSetupDone(String inputId) { |
| boolean done = mSetUpInputs.contains(inputId); |
| if (DEBUG) { |
| Log.d(TAG, "isSetupDone: (input=" + inputId + ", result= " + done + ")"); |
| } |
| return done; |
| } |
| |
| /** |
| * Returns true, if there is any newly installed input. |
| */ |
| public boolean hasNewInput(TvInputManagerHelper inputManager) { |
| for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { |
| if (isNewInput(input.getId())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks whether the given input is already recognized by the user or not. |
| */ |
| private boolean isRecognizedInput(String inputId) { |
| return mRecognizedInputs.contains(inputId); |
| } |
| |
| /** |
| * Marks all the inputs as recognized inputs. Once it is marked, {@link #isRecognizedInput} will |
| * return {@code true}. |
| */ |
| public void markAllInputsRecognized(TvInputManagerHelper inputManager) { |
| for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { |
| mRecognizedInputs.add(input.getId()); |
| } |
| mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) |
| .apply(); |
| } |
| |
| /** |
| * Checks whether there are any unrecognized inputs. |
| */ |
| public boolean hasUnrecognizedInput(TvInputManagerHelper inputManager) { |
| for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { |
| if (!isRecognizedInput(input.getId())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Grants permission for writing EPG data to all verified packages. |
| * |
| * @param context The Context used for granting permission. |
| */ |
| public static void grantEpgPermissionToSetUpPackages(Context context) { |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
| // Can't grant permission. |
| return; |
| } |
| |
| // Find all already-verified packages. |
| Set<String> setUpPackages = new HashSet<>(); |
| SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); |
| for (String input : sp.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.<String>emptySet())) { |
| if (!TextUtils.isEmpty(input)) { |
| ComponentName componentName = ComponentName.unflattenFromString(input); |
| if (componentName != null) { |
| setUpPackages.add(componentName.getPackageName()); |
| } |
| } |
| } |
| |
| for (String packageName : setUpPackages) { |
| grantEpgPermission(context, packageName); |
| } |
| } |
| |
| /** |
| * Grants permission for writing EPG data to a given package. |
| * |
| * @param context The Context used for granting permission. |
| * @param packageName The name of the package to give permission. |
| */ |
| public static void grantEpgPermission(Context context, String packageName) { |
| // TvProvider allows granting of Uri permissions starting from MNC. |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| if (DEBUG) { |
| Log.d(TAG, "grantEpgPermission(context=" + context + ", packageName=" + packageName |
| + ")"); |
| } |
| try { |
| int modeFlags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
| | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; |
| context.grantUriPermission(packageName, TvContract.Channels.CONTENT_URI, modeFlags); |
| context.grantUriPermission(packageName, TvContract.Programs.CONTENT_URI, modeFlags); |
| } catch (SecurityException e) { |
| Log.e(TAG, "Either TvProvider does not allow granting of Uri permissions or the app" |
| + " does not have permission.", e); |
| } |
| } |
| } |
| |
| /** |
| * Called when Live channels app is launched. Once it is called, {@link |
| * #isFirstTune} will return false. |
| */ |
| public void onTuned() { |
| if (!mIsFirstTune) { |
| return; |
| } |
| mIsFirstTune = false; |
| mSharedPreferences.edit().putBoolean(PREF_KEY_IS_FIRST_TUNE, false).apply(); |
| } |
| |
| /** |
| * Called when input list is changed. It mainly handles input removals. |
| */ |
| public void onInputListUpdated(TvInputManager manager) { |
| // mRecognizedInputs > mKnownInputs > mSetUpInputs. |
| Set<String> removedInputList = new HashSet<>(mRecognizedInputs); |
| for (TvInputInfo input : manager.getTvInputList()) { |
| removedInputList.remove(input.getId()); |
| } |
| // A USB tuner device can be temporarily unplugged. We do not remove the USB tuner input |
| // from the known inputs so that the input won't appear as a new input whenever the user |
| // plugs in the USB tuner device again. |
| removedInputList.remove(mUsbTunerInputId); |
| |
| if (!removedInputList.isEmpty()) { |
| for (String input : removedInputList) { |
| mRecognizedInputs.remove(input); |
| mSetUpInputs.remove(input); |
| mKnownInputs.remove(input); |
| } |
| mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs) |
| .putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs) |
| .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply(); |
| } |
| } |
| |
| /** |
| * Called when an setup is done. Once it is called, {@link #isSetupDone} returns {@code true} |
| * for {@code inputId}. |
| */ |
| public void onSetupDone(String inputId) { |
| SoftPreconditions.checkState(inputId != null); |
| if (DEBUG) Log.d(TAG, "onSetupDone: input=" + inputId); |
| if (!mRecognizedInputs.contains(inputId)) { |
| Log.i(TAG, "An unrecognized input's setup has been done. inputId=" + inputId); |
| mRecognizedInputs.add(inputId); |
| mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) |
| .apply(); |
| } |
| if (!mKnownInputs.contains(inputId)) { |
| Log.i(TAG, "An unknown input's setup has been done. inputId=" + inputId); |
| mKnownInputs.add(inputId); |
| mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs).apply(); |
| } |
| if (!mSetUpInputs.contains(inputId)) { |
| mSetUpInputs.add(inputId); |
| mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply(); |
| } |
| } |
| } |