aboutsummaryrefslogtreecommitdiff
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/tv/ChannelTuner.java2
-rw-r--r--src/com/android/tv/InputSessionManager.java7
-rw-r--r--src/com/android/tv/MainActivity.java82
-rw-r--r--src/com/android/tv/SetupPassthroughActivity.java2
-rw-r--r--src/com/android/tv/audiotvservice/AudioOnlyTvService.java7
-rw-r--r--src/com/android/tv/data/ChannelImpl.java14
-rw-r--r--src/com/android/tv/data/StreamInfo.java2
-rwxr-xr-xsrc/com/android/tv/dialog/InteractiveAppDialogFragment.java138
-rw-r--r--src/com/android/tv/dvr/recorder/RecordingScheduler.java3
-rw-r--r--src/com/android/tv/features/TvFeatures.java3
-rw-r--r--src/com/android/tv/interactive/IAppManager.java428
-rw-r--r--src/com/android/tv/onboarding/OnboardingActivity.java2
-rw-r--r--src/com/android/tv/receiver/AudioCapabilitiesReceiver.java3
-rw-r--r--src/com/android/tv/receiver/BootCompletedReceiver.java5
-rw-r--r--src/com/android/tv/setup/SystemSetupActivity.java3
-rw-r--r--src/com/android/tv/ui/SelectInputView.java12
-rw-r--r--src/com/android/tv/ui/TunableTvView.java51
-rw-r--r--src/com/android/tv/ui/TvOverlayManager.java2
-rwxr-xr-xsrc/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java54
-rw-r--r--src/com/android/tv/ui/sidepanel/SettingsFragment.java17
-rw-r--r--src/com/android/tv/util/SetupUtils.java50
-rw-r--r--src/com/android/tv/util/TvSettings.java15
22 files changed, 880 insertions, 22 deletions
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java
index fe138980..351f0010 100644
--- a/src/com/android/tv/ChannelTuner.java
+++ b/src/com/android/tv/ChannelTuner.java
@@ -36,7 +36,7 @@ import java.util.Map;
import java.util.Set;
/**
- * It manages the current tuned channel among browsable channels. And it determines the next channel
+ * Manages the current tuned channel among browsable channels, and determines the next channel
* by channel up/down. But, it doesn't actually tune through TvView.
*/
@MainThread
diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java
index ea17751b..57fc883e 100644
--- a/src/com/android/tv/InputSessionManager.java
+++ b/src/com/android/tv/InputSessionManager.java
@@ -18,6 +18,7 @@ package com.android.tv;
import android.annotation.TargetApi;
import android.content.Context;
+import android.media.tv.AitInfo;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
import android.media.tv.TvTrackInfo;
@@ -582,6 +583,12 @@ public class InputSessionManager {
public void onSignalStrength(String inputId, int value) {
mDelegate.onSignalStrength(inputId, value);
}
+
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Override
+ public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+ mDelegate.onAitInfoUpdated(inputId, aitInfo);
+ }
}
/** Called when the {@link TvView} channel is changed. */
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 8dbafe47..cea293de 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -18,6 +18,7 @@ package com.android.tv;
import static com.android.tv.common.feature.SystemAppFeature.SYSTEM_APP_FEATURE;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.SearchManager;
@@ -32,6 +33,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.hardware.display.DisplayManager;
+import android.media.tv.AitInfo;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
@@ -40,6 +42,8 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView.OnUnhandledInputEventListener;
+import android.media.tv.interactive.TvInteractiveAppManager;
+import android.media.tv.interactive.TvInteractiveAppView;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -105,6 +109,8 @@ import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
import com.android.tv.dialog.SafeDismissDialogFragment;
+import com.android.tv.dialog.InteractiveAppDialogFragment;
+import com.android.tv.dialog.InteractiveAppDialogFragment.OnInteractiveAppCheckedListener;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.recorder.ConflictChecker;
@@ -115,6 +121,7 @@ import com.android.tv.dvr.ui.DvrStopRecordingFragment;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.features.TvFeatures;
import com.android.tv.guide.ProgramItemView;
+import com.android.tv.interactive.IAppManager;
import com.android.tv.menu.Menu;
import com.android.tv.onboarding.OnboardingActivity;
import com.android.tv.parental.ContentRatingsManager;
@@ -193,7 +200,8 @@ public class MainActivity extends Activity
OnPinCheckedListener,
ChannelChanger,
HasSingletons<MySingletons>,
- HasAndroidInjector {
+ HasAndroidInjector,
+ OnInteractiveAppCheckedListener {
private static final String TAG = "MainActivity";
private static final boolean DEBUG = false;
private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
@@ -254,6 +262,9 @@ public class MainActivity extends Activity
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_ON);
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
+ if (Build.VERSION.SDK_INT > 33) { // TIRAMISU
+ SYSTEM_INTENT_FILTER.addAction(TvInteractiveAppManager.ACTION_APP_LINK_COMMAND);
+ }
}
private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
@@ -365,6 +376,8 @@ public class MainActivity extends Activity
private String mLastInputIdFromIntent;
+ private IAppManager mIAppManager;
+
private final Handler mHandler = new MainActivityHandler(this);
private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
@@ -406,6 +419,13 @@ public class MainActivity extends Activity
tune(true);
}
break;
+ case TvInteractiveAppManager.ACTION_APP_LINK_COMMAND:
+ if (DEBUG) {
+ Log.d(TAG, "Received action link command");
+ }
+ // TODO: handle the command
+ break;
+
default: // fall out
}
}
@@ -545,8 +565,10 @@ public class MainActivity extends Activity
return;
}
setContentView(R.layout.activity_tv);
+ TvInteractiveAppView tvInteractiveAppView = findViewById(R.id.tv_app_view);
mTvView = findViewById(R.id.main_tunable_tv_view);
- mTvView.initialize(mProgramDataManager, mTvInputManagerHelper, mLegacyFlags);
+ mTvView.initialize(
+ mProgramDataManager, mTvInputManagerHelper, mLegacyFlags, tvInteractiveAppView);
mTvView.setOnUnhandledInputEventListener(
new OnUnhandledInputEventListener() {
@Override
@@ -717,8 +739,8 @@ public class MainActivity extends Activity
mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, null);
mAudioCapabilitiesReceiver.register();
Intent nowPlayingIntent = new Intent(this, MainActivity.class);
- PendingIntent pendingIntent =
- PendingIntent.getActivity(this, REQUEST_CODE_NOW_PLAYING, nowPlayingIntent, 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, REQUEST_CODE_NOW_PLAYING,
+ nowPlayingIntent, PendingIntent.FLAG_IMMUTABLE);
mMediaSessionWrapper = new MediaSessionWrapper(this, pendingIntent);
mTvViewUiManager.restoreDisplayMode(false);
@@ -732,9 +754,21 @@ public class MainActivity extends Activity
mDvrConflictChecker = new ConflictChecker(this);
}
initForTest();
+ if (TvFeatures.HAS_TIAF.isEnabled(this)) {
+ mIAppManager = new IAppManager(this, mTvView, mHandler);
+ }
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end");
}
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Override
+ public void onInteractiveAppChecked(boolean checked) {
+ TvSettings.setTvIAppOn(getApplicationContext(), checked);
+ if (checked) {
+ mIAppManager.processHeldAitInfo();
+ }
+ }
+
private void startOnboardingActivity() {
startActivity(OnboardingActivity.buildIntent(this, getIntent()));
finish();
@@ -833,7 +867,7 @@ public class MainActivity extends Activity
mMainDurationTimer.start();
applyParentalControlSettings();
- registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER);
+ registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER, Context.RECEIVER_EXPORTED);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Intent notificationIntent = new Intent(this, NotificationService.class);
@@ -1081,7 +1115,7 @@ public class MainActivity extends Activity
}
mTvView.start();
- mAudioManagerHelper.setVolumeByAudioFocusStatus();
+ mAudioManagerHelper.requestAudioFocus();
tune(true);
}
@@ -1126,6 +1160,9 @@ public class MainActivity extends Activity
private void stopAll(boolean keepVisibleBehind) {
mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
stopTv("stopAll()", keepVisibleBehind);
+ if (mIAppManager != null) {
+ mIAppManager.stop();
+ }
}
public TvInputManagerHelper getTvInputManagerHelper() {
@@ -1138,7 +1175,7 @@ public class MainActivity extends Activity
* @param calledByPopup If true, startSetupActivity is invoked from the setup fragment.
*/
public void startSetupActivity(TvInputInfo input, boolean calledByPopup) {
- Intent intent = CommonUtils.createSetupIntent(input);
+ Intent intent = mSetupUtils.createSetupIntent(this, input);
if (intent == null) {
Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
return;
@@ -1425,6 +1462,9 @@ public class MainActivity extends Activity
if (DeveloperPreferences.LOG_KEYEVENT.get(this)) {
Log.d(TAG, "dispatchKeyEvent(" + event + ")");
}
+ if (mIAppManager != null && mIAppManager.dispatchKeyEvent(event)) {
+ return true;
+ }
// If an activity is closed on a back key down event, back key down events with none zero
// repeat count or a back key up event can be happened without the first back key down
// event which should be ignored in this activity.
@@ -1631,7 +1671,7 @@ public class MainActivity extends Activity
}
}
- private void stopTv() {
+ public void stopTv() {
stopTv(null, false);
}
@@ -1932,12 +1972,21 @@ public class MainActivity extends Activity
@VisibleForTesting
protected void applyMultiAudio(String trackId) {
+ applyMultiAudio(false, trackId);
+ }
+
+ @VisibleForTesting
+ protected void applyMultiAudio(boolean allowAutoSelection, String trackId) {
+ if (!allowAutoSelection && trackId == null) {
+ selectTrack(TvTrackInfo.TYPE_AUDIO, null, UNDEFINED_TRACK_INDEX);
+ mTvOptionsManager.onMultiAudioChanged(null);
+ return;
+ }
List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
if (tracks == null) {
mTvOptionsManager.onMultiAudioChanged(null);
return;
}
-
TvTrackInfo bestTrack = null;
if (trackId != null) {
for (TvTrackInfo track : tracks) {
@@ -2459,7 +2508,7 @@ public class MainActivity extends Activity
return handled;
}
- private boolean isKeyEventBlocked() {
+ public boolean isKeyEventBlocked() {
// If the current channel is a passthrough channel, we don't handle the key events in TV
// activity. Instead, the key event will be handled by the passthrough TV input.
return mChannelTuner.isCurrentChannelPassthrough();
@@ -2907,7 +2956,7 @@ public class MainActivity extends Activity
}
applyDisplayRefreshRate(info.getVideoFrameRate());
mTvViewUiManager.updateTvAspectRatio();
- applyMultiAudio(
+ applyMultiAudio(allowAutoSelectionOfTrack,
allowAutoSelectionOfTrack ? null : getSelectedTrack(TvTrackInfo.TYPE_AUDIO));
applyClosedCaption();
mOverlayManager.getMenu().onStreamInfoChanged();
@@ -2989,6 +3038,14 @@ public class MainActivity extends Activity
TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH);
}
}
+
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Override
+ public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+ if (mIAppManager != null) {
+ mIAppManager.onAitInfoUpdated(aitInfo);
+ }
+ }
}
private class MySingletonsImpl implements MySingletons {
@@ -3047,5 +3104,8 @@ public class MainActivity extends Activity
@ContributesAndroidInjector
abstract DvrScheduleFragment contributesDvrScheduleFragment();
+
+ @ContributesAndroidInjector
+ abstract InteractiveAppDialogFragment contributesInteractiveAppDialogFragment();
}
}
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
index e7f89108..2a4a556f 100644
--- a/src/com/android/tv/SetupPassthroughActivity.java
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -109,7 +109,6 @@ public class SetupPassthroughActivity extends Activity {
finish();
return;
}
- SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName);
if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion);
// If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during
// setupIntent.putExtras(intent.getExtras()).
@@ -127,6 +126,7 @@ public class SetupPassthroughActivity extends Activity {
finish();
return;
}
+ SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName);
startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Can't find activity: " + setupIntent.getComponent());
diff --git a/src/com/android/tv/audiotvservice/AudioOnlyTvService.java b/src/com/android/tv/audiotvservice/AudioOnlyTvService.java
index 5d0e9c82..59e2406f 100644
--- a/src/com/android/tv/audiotvservice/AudioOnlyTvService.java
+++ b/src/com/android/tv/audiotvservice/AudioOnlyTvService.java
@@ -15,11 +15,14 @@
*/
package com.android.tv.audiotvservice;
+import android.annotation.TargetApi;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.media.session.MediaSession;
+import android.media.tv.AitInfo;
import android.net.Uri;
+import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -99,4 +102,8 @@ public class AudioOnlyTvService extends Service implements OnTuneListener {
@Override
public void onChannelSignalStrength() {}
+
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Override
+ public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {}
}
diff --git a/src/com/android/tv/data/ChannelImpl.java b/src/com/android/tv/data/ChannelImpl.java
index f31290d0..5be1179d 100644
--- a/src/com/android/tv/data/ChannelImpl.java
+++ b/src/com/android/tv/data/ChannelImpl.java
@@ -18,6 +18,7 @@ package com.android.tv.data;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.media.tv.TvContract;
@@ -673,7 +674,18 @@ public final class ChannelImpl implements Channel {
if (!TextUtils.isEmpty(mAppLinkText) && !TextUtils.isEmpty(mAppLinkIntentUri)) {
try {
Intent intent = Intent.parseUri(mAppLinkIntentUri, Intent.URI_INTENT_SCHEME);
- if (intent.resolveActivityInfo(pm, 0) != null) {
+ ActivityInfo activityInfo = intent.resolveActivityInfo(pm, 0);
+ if (activityInfo != null) {
+ String packageName = activityInfo.packageName;
+ // Prevent creation of App Links to private activities in this package
+ boolean isProtectedActivity = packageName != null
+ && (packageName.equals(CommonConstants.BASE_PACKAGE)
+ || packageName.startsWith(CommonConstants.BASE_PACKAGE + "."));
+ if (isProtectedActivity) {
+ Log.w(TAG,"Attempt to add app link to protected activity: "
+ + mAppLinkIntentUri);
+ return;
+ }
mAppLinkIntent = intent;
mAppLinkIntent.putExtra(
CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java
index e4237bf4..f323423c 100644
--- a/src/com/android/tv/data/StreamInfo.java
+++ b/src/com/android/tv/data/StreamInfo.java
@@ -44,6 +44,8 @@ public interface StreamInfo {
int getAudioChannelCount();
+ float getStreamVolume();
+
boolean hasClosedCaption();
boolean isVideoAvailable();
diff --git a/src/com/android/tv/dialog/InteractiveAppDialogFragment.java b/src/com/android/tv/dialog/InteractiveAppDialogFragment.java
new file mode 100755
index 00000000..c5ffbaac
--- /dev/null
+++ b/src/com/android/tv/dialog/InteractiveAppDialogFragment.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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.dialog;
+
+import android.annotation.TargetApi;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.tv.R;
+import com.android.tv.common.SoftPreconditions;
+
+import java.util.function.Function;
+
+import dagger.android.AndroidInjection;
+
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class InteractiveAppDialogFragment extends SafeDismissDialogFragment {
+ private static final boolean DEBUG = false;
+
+ public static final String DIALOG_TAG = InteractiveAppDialogFragment.class.getName();
+ private static final String TRACKER_LABEL = "Interactive App Dialog";
+ private static final String TV_IAPP_NAME = "tv_iapp_name";
+ private boolean mIsChoseOK;
+ private String mIAppName;
+ private Function mUpdateAitInfo;
+
+ public static InteractiveAppDialogFragment create(String iappName) {
+ InteractiveAppDialogFragment fragment = new InteractiveAppDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(TV_IAPP_NAME, iappName);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ AndroidInjection.inject(this);
+ super.onAttach(context);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mIAppName = getArguments().getString(TV_IAPP_NAME);
+ setStyle(STYLE_NO_TITLE, 0);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dlg = super.onCreateDialog(savedInstanceState);
+ dlg.getWindow().getAttributes().windowAnimations = R.style.pin_dialog_animation;
+ mIsChoseOK = false;
+ return dlg;
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Dialog size is determined by its windows size, not inflated view size.
+ // So apply view size to window after the DialogFragment.onStart() where dialog is shown.
+ Dialog dlg = getDialog();
+ if (dlg != null) {
+ dlg.getWindow()
+ .setLayout(
+ getResources().getDimensionPixelSize(R.dimen.pin_dialog_width),
+ LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View v = inflater.inflate(R.layout.tv_app_dialog, container, false);
+ TextView mTitleView = (TextView) v.findViewById(R.id.title);
+ mTitleView.setText(getString(R.string.tv_app_dialog_title, mIAppName));
+ Button okButton = v.findViewById(R.id.ok);
+ okButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ exit(true);
+ }
+ });
+ Button cancelButton = v.findViewById(R.id.cancel);
+ cancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ exit(false);
+ }
+ });
+ return v;
+ }
+
+ private void exit(boolean isokclick) {
+ mIsChoseOK = isokclick;
+ dismiss();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ SoftPreconditions.checkState(getActivity() instanceof OnInteractiveAppCheckedListener);
+ if (getActivity() instanceof OnInteractiveAppCheckedListener) {
+ ((OnInteractiveAppCheckedListener) getActivity())
+ .onInteractiveAppChecked(mIsChoseOK);
+ }
+ }
+
+ public interface OnInteractiveAppCheckedListener {
+ void onInteractiveAppChecked(boolean checked);
+ }
+}
diff --git a/src/com/android/tv/dvr/recorder/RecordingScheduler.java b/src/com/android/tv/dvr/recorder/RecordingScheduler.java
index f309537d..475c17f8 100644
--- a/src/com/android/tv/dvr/recorder/RecordingScheduler.java
+++ b/src/com/android/tv/dvr/recorder/RecordingScheduler.java
@@ -322,7 +322,8 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
long wakeAt = nextStartTime - MS_TO_WAKE_BEFORE_START;
if (DEBUG) Log.d(TAG, "Set alarm to record at " + wakeAt);
Intent intent = new Intent(mContext, DvrStartRecordingReceiver.class);
- PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ PendingIntent alarmIntent =
+ PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
// This will cancel the previous alarm.
mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, wakeAt, alarmIntent);
} else {
diff --git a/src/com/android/tv/features/TvFeatures.java b/src/com/android/tv/features/TvFeatures.java
index 5282c28c..ebd7cb9a 100644
--- a/src/com/android/tv/features/TvFeatures.java
+++ b/src/com/android/tv/features/TvFeatures.java
@@ -101,5 +101,8 @@ public final class TvFeatures extends CommonFeatures {
/** Use input blocklist to disable partner's tuner input. */
public static final Feature USE_PARTNER_INPUT_BLOCKLIST = ON;
+ /** Support for interactive applications using the TIAF **/
+ public static final Feature HAS_TIAF = Sdk.AT_LEAST_T;
+
private TvFeatures() {}
}
diff --git a/src/com/android/tv/interactive/IAppManager.java b/src/com/android/tv/interactive/IAppManager.java
new file mode 100644
index 00000000..682b35c6
--- /dev/null
+++ b/src/com/android/tv/interactive/IAppManager.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2022 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.interactive;
+
+import static com.android.tv.util.CaptionSettings.OPTION_OFF;
+import static com.android.tv.util.CaptionSettings.OPTION_ON;
+
+import android.annotation.TargetApi;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.interactive.TvInteractiveAppManager;
+import android.media.tv.AitInfo;
+import android.media.tv.interactive.TvInteractiveAppService;
+import android.media.tv.interactive.TvInteractiveAppServiceInfo;
+import android.media.tv.interactive.TvInteractiveAppView;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.util.ContentUriUtils;
+import com.android.tv.data.api.Channel;
+import com.android.tv.dialog.InteractiveAppDialogFragment;
+import com.android.tv.features.TvFeatures;
+import com.android.tv.ui.TunableTvView;
+import com.android.tv.util.TvSettings;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class IAppManager {
+ private static final String TAG = "IAppManager";
+ private static final boolean DEBUG = false;
+
+ private final MainActivity mMainActivity;
+ private final TvInteractiveAppManager mTvIAppManager;
+ private final TvInteractiveAppView mTvIAppView;
+ private final TunableTvView mTvView;
+ private final Handler mHandler;
+ private AitInfo mCurrentAitInfo;
+ private AitInfo mHeldAitInfo; // AIT info that has been held pending dialog confirmation
+ private boolean mTvAppDialogShown = false;
+
+ public IAppManager(@NonNull MainActivity parentActivity, @NonNull TunableTvView tvView,
+ @NonNull Handler handler) {
+ SoftPreconditions.checkFeatureEnabled(parentActivity, TvFeatures.HAS_TIAF, TAG);
+
+ mMainActivity = parentActivity;
+ mTvView = tvView;
+ mHandler = handler;
+ mTvIAppManager = mMainActivity.getSystemService(TvInteractiveAppManager.class);
+ mTvIAppView = mMainActivity.findViewById(R.id.tv_app_view);
+ if (mTvIAppManager == null || mTvIAppView == null) {
+ Log.e(TAG, "Could not find interactive app view or manager");
+ return;
+ }
+
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ mTvIAppManager.registerCallback(
+ executor,
+ new MyInteractiveAppManagerCallback()
+ );
+ mTvIAppView.setCallback(
+ executor,
+ new MyInteractiveAppViewCallback()
+ );
+ mTvIAppView.setOnUnhandledInputEventListener(executor,
+ inputEvent -> {
+ if (mMainActivity.isKeyEventBlocked()) {
+ return true;
+ }
+ if (inputEvent instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) inputEvent;
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && keyEvent.isLongPress()) {
+ if (mMainActivity.onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
+ return true;
+ }
+ }
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ return mMainActivity.onKeyUp(keyEvent.getKeyCode(), keyEvent);
+ } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ return mMainActivity.onKeyDown(keyEvent.getKeyCode(), keyEvent);
+ }
+ }
+ return false;
+ });
+ }
+
+ public void stop() {
+ mTvIAppView.stopInteractiveApp();
+ mTvIAppView.reset();
+ mCurrentAitInfo = null;
+ }
+
+ /*
+ * Update current info based on ait info that was held when the dialog was shown.
+ */
+ public void processHeldAitInfo() {
+ if (mHeldAitInfo != null) {
+ onAitInfoUpdated(mHeldAitInfo);
+ }
+ }
+
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mTvIAppView != null && mTvIAppView.getVisibility() == View.VISIBLE
+ && mTvIAppView.dispatchKeyEvent(event)){
+ return true;
+ }
+ return false;
+ }
+
+ public void onAitInfoUpdated(AitInfo aitInfo) {
+ if (mTvIAppManager == null || aitInfo == null) {
+ return;
+ }
+ if (mCurrentAitInfo != null && mCurrentAitInfo.getType() == aitInfo.getType()) {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring AIT update: Same type as current");
+ }
+ return;
+ }
+
+ List<TvInteractiveAppServiceInfo> tvIAppInfoList =
+ mTvIAppManager.getTvInteractiveAppServiceList();
+ if (tvIAppInfoList.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring AIT update: No interactive app services registered");
+ }
+ return;
+ }
+
+ // App Type ID numbers allocated by DVB Services
+ int type = -1;
+ switch (aitInfo.getType()) {
+ case 0x0010: // HBBTV
+ type = TvInteractiveAppServiceInfo.INTERACTIVE_APP_TYPE_HBBTV;
+ break;
+ case 0x0006: // DCAP-J: DCAP Java applications
+ case 0x0007: // DCAP-X: DCAP XHTML applications
+ type = TvInteractiveAppServiceInfo.INTERACTIVE_APP_TYPE_ATSC;
+ break;
+ case 0x0001: // Ginga-J
+ case 0x0009: // Ginga-NCL
+ case 0x000b: // Ginga-HTML5
+ type = TvInteractiveAppServiceInfo.INTERACTIVE_APP_TYPE_GINGA;
+ break;
+ default:
+ Log.e(TAG, "AIT info contained unknown type: " + aitInfo.getType());
+ return;
+ }
+
+ if (TvSettings.isTvIAppOn(mMainActivity.getApplicationContext())) {
+ mTvAppDialogShown = false;
+ for (TvInteractiveAppServiceInfo info : tvIAppInfoList) {
+ if ((info.getSupportedTypes() & type) > 0) {
+ mCurrentAitInfo = aitInfo;
+ if (mTvIAppView != null) {
+ mTvIAppView.setVisibility(View.VISIBLE);
+ mTvIAppView.prepareInteractiveApp(info.getId(), type);
+ }
+ break;
+ }
+ }
+ } else if (!mTvAppDialogShown) {
+ if (DEBUG) {
+ Log.d(TAG, "TV IApp is not enabled");
+ }
+
+ for (TvInteractiveAppServiceInfo info : tvIAppInfoList) {
+ if ((info.getSupportedTypes() & type) > 0) {
+ mMainActivity.getOverlayManager().showDialogFragment(
+ InteractiveAppDialogFragment.DIALOG_TAG,
+ InteractiveAppDialogFragment.create(info.getServiceInfo().packageName),
+ false);
+ mHeldAitInfo = aitInfo;
+ mTvAppDialogShown = true;
+ break;
+ }
+ }
+ }
+ }
+
+ private class MyInteractiveAppManagerCallback extends
+ TvInteractiveAppManager.TvInteractiveAppCallback {
+ @Override
+ public void onInteractiveAppServiceAdded(String iAppServiceId) {}
+
+ @Override
+ public void onInteractiveAppServiceRemoved(String iAppServiceId) {}
+
+ @Override
+ public void onInteractiveAppServiceUpdated(String iAppServiceId) {}
+
+ @Override
+ public void onTvInteractiveAppServiceStateChanged(String iAppServiceId, int type, int state,
+ int err) {
+ if (state == TvInteractiveAppManager.SERVICE_STATE_READY && mTvIAppView != null) {
+ mTvIAppView.startInteractiveApp();
+ mTvIAppView.setTvView(mTvView.getTvView());
+ if (mTvView.getTvView() != null) {
+ mTvView.getTvView().setInteractiveAppNotificationEnabled(true);
+ }
+ }
+ }
+ }
+
+ private class MyInteractiveAppViewCallback extends
+ TvInteractiveAppView.TvInteractiveAppCallback {
+ @Override
+ public void onPlaybackCommandRequest(String iAppServiceId, String cmdType,
+ Bundle parameters) {
+ if (mTvView == null || cmdType == null) {
+ return;
+ }
+ switch (cmdType) {
+ case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE:
+ if (parameters == null) {
+ return;
+ }
+ String uriString = parameters.getString(
+ TvInteractiveAppService.COMMAND_PARAMETER_KEY_CHANNEL_URI);
+ if (uriString != null) {
+ Uri channelUri = Uri.parse(uriString);
+ Channel channel = mMainActivity.getChannelDataManager().getChannel(
+ ContentUriUtils.safeParseId(channelUri));
+ if (channel != null) {
+ mHandler.post(() -> mMainActivity.tuneToChannel(channel));
+ }
+ }
+ break;
+ case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_SELECT_TRACK:
+ if (mTvView != null && parameters != null) {
+ int trackType = parameters.getInt(
+ TvInteractiveAppService.COMMAND_PARAMETER_KEY_TRACK_TYPE,
+ -1);
+ String trackId = parameters.getString(
+ TvInteractiveAppService.COMMAND_PARAMETER_KEY_TRACK_ID,
+ null);
+ switch (trackType) {
+ case TvTrackInfo.TYPE_AUDIO:
+ // When trackId is null, deselects current audio track.
+ mHandler.post(() -> mMainActivity.selectAudioTrack(trackId));
+ break;
+ case TvTrackInfo.TYPE_SUBTITLE:
+ // When trackId is null, turns off captions.
+ mHandler.post(() -> mMainActivity.selectSubtitleTrack(
+ trackId == null ? OPTION_OFF : OPTION_ON, trackId));
+ break;
+ }
+ }
+ break;
+ case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME:
+ if (parameters == null) {
+ return;
+ }
+ float volume = parameters.getFloat(
+ TvInteractiveAppService.COMMAND_PARAMETER_KEY_VOLUME, -1);
+ if (volume >= 0.0 && volume <= 1.0) {
+ mHandler.post(() -> mTvView.setStreamVolume(volume));
+ }
+ break;
+ case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_NEXT:
+ mHandler.post(mMainActivity::channelUp);
+ break;
+ case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_PREV:
+ mHandler.post(mMainActivity::channelDown);
+ break;
+ case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP:
+ int mode = 1; // TvInteractiveAppService.COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK
+ if (parameters != null) {
+ mode = parameters.getInt(
+ /* TvInteractiveAppService.COMMAND_PARAMETER_KEY_STOP_MODE */
+ "command_stop_mode",
+ /*TvInteractiveAppService.COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK*/
+ 1);
+ }
+ mHandler.post(mMainActivity::stopTv);
+ break;
+ default:
+ Log.e(TAG, "PlaybackCommandRequest had unknown cmdType:"
+ + cmdType);
+ break;
+ }
+ }
+
+ @Override
+ public void onStateChanged(String iAppServiceId, int state, int err) {
+ }
+
+ @Override
+ public void onBiInteractiveAppCreated(String iAppServiceId, Uri biIAppUri,
+ String biIAppId) {}
+
+ @Override
+ public void onTeletextAppStateChanged(String iAppServiceId, int state) {}
+
+ @Override
+ public void onSetVideoBounds(String iAppServiceId, Rect rect) {
+ if (mTvView != null) {
+ ViewGroup.MarginLayoutParams layoutParams = mTvView.getTvViewLayoutParams();
+ layoutParams.setMargins(rect.left, rect.top, rect.right, rect.bottom);
+ mTvView.setTvViewLayoutParams(layoutParams);
+ }
+ }
+
+ @Override
+ @TargetApi(34)
+ public void onRequestCurrentVideoBounds(@NonNull String iAppServiceId) {
+ mHandler.post(
+ () -> {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentVideoBounds service ID = "
+ + iAppServiceId);
+ }
+ Rect bounds = new Rect(mTvView.getLeft(), mTvView.getTop(),
+ mTvView.getRight(), mTvView.getBottom());
+ mTvIAppView.sendCurrentVideoBounds(bounds);
+ });
+ }
+
+ @Override
+ public void onRequestCurrentChannelUri(String iAppServiceId) {
+ if (mTvIAppView == null) {
+ return;
+ }
+ Channel currentChannel = mMainActivity.getCurrentChannel();
+ Uri currentUri = (currentChannel == null)
+ ? null
+ : currentChannel.getUri();
+ mTvIAppView.sendCurrentChannelUri(currentUri);
+ }
+
+ @Override
+ public void onRequestCurrentChannelLcn(String iAppServiceId) {
+ if (mTvIAppView == null) {
+ return;
+ }
+ Channel currentChannel = mMainActivity.getCurrentChannel();
+ if (currentChannel == null || currentChannel.getDisplayNumber() == null) {
+ return;
+ }
+ // Expected format is major channel number, delimiter, minor channel number
+ String displayNumber = currentChannel.getDisplayNumber();
+ String format = "[0-9]+" + Channel.CHANNEL_NUMBER_DELIMITER + "[0-9]+";
+ if (!displayNumber.matches(format)) {
+ return;
+ }
+ // Major channel number is returned
+ String[] numbers = displayNumber.split(
+ String.valueOf(Channel.CHANNEL_NUMBER_DELIMITER));
+ mTvIAppView.sendCurrentChannelLcn(Integer.parseInt(numbers[0]));
+ }
+
+ @Override
+ public void onRequestStreamVolume(String iAppServiceId) {
+ if (mTvIAppView == null || mTvView == null) {
+ return;
+ }
+ mTvIAppView.sendStreamVolume(mTvView.getStreamVolume());
+ }
+
+ @Override
+ public void onRequestTrackInfoList(String iAppServiceId) {
+ if (mTvIAppView == null || mTvView == null) {
+ return;
+ }
+ List<TvTrackInfo> allTracks = new ArrayList<>();
+ int[] trackTypes = new int[] {TvTrackInfo.TYPE_AUDIO,
+ TvTrackInfo.TYPE_VIDEO, TvTrackInfo.TYPE_SUBTITLE};
+
+ for (int trackType : trackTypes) {
+ List<TvTrackInfo> currentTracks = mTvView.getTracks(trackType);
+ if (currentTracks == null) {
+ continue;
+ }
+ for (TvTrackInfo track : currentTracks) {
+ if (track != null) {
+ allTracks.add(track);
+ }
+ }
+ }
+ mTvIAppView.sendTrackInfoList(allTracks);
+ }
+
+ @Override
+ public void onRequestCurrentTvInputId(String iAppServiceId) {
+ if (mTvIAppView == null) {
+ return;
+ }
+ Channel currentChannel = mMainActivity.getCurrentChannel();
+ String currentInputId = (currentChannel == null)
+ ? null
+ : currentChannel.getInputId();
+ mTvIAppView.sendCurrentTvInputId(currentInputId);
+ }
+
+ @Override
+ public void onRequestSigning(String iAppServiceId, String signingId, String algorithm,
+ String alias, byte[] data) {}
+ }
+}
diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java
index dd386d81..0ce5d931 100644
--- a/src/com/android/tv/onboarding/OnboardingActivity.java
+++ b/src/com/android/tv/onboarding/OnboardingActivity.java
@@ -193,7 +193,7 @@ public class OnboardingActivity extends SetupActivity {
params.getString(
SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- Intent intent = CommonUtils.createSetupIntent(input);
+ Intent intent = mSetupUtils.createSetupIntent(this, input);
if (intent == null) {
Toast.makeText(
this,
diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
index 5fa7606d..9578e243 100644
--- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -67,7 +67,8 @@ public final class AudioCapabilitiesReceiver {
}
public void register() {
- mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
+ mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG),
+ Context.RECEIVER_EXPORTED);
}
public void unregister() {
diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java
index 0eb03bec..0bf6ecf3 100644
--- a/src/com/android/tv/receiver/BootCompletedReceiver.java
+++ b/src/com/android/tv/receiver/BootCompletedReceiver.java
@@ -56,6 +56,11 @@ public class BootCompletedReceiver extends BroadcastReceiver {
Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
return;
}
+ String action = intent.getAction();
+ if (!Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ Log.w(TAG, "invalid action " + action);
+ return;
+ }
if (DEBUG) Log.d(TAG, "boot completed " + intent);
Starter.start(context);
diff --git a/src/com/android/tv/setup/SystemSetupActivity.java b/src/com/android/tv/setup/SystemSetupActivity.java
index 7bf04692..b39ac4ea 100644
--- a/src/com/android/tv/setup/SystemSetupActivity.java
+++ b/src/com/android/tv/setup/SystemSetupActivity.java
@@ -53,6 +53,7 @@ public class SystemSetupActivity extends SetupActivity {
private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
@Inject TvInputManagerHelper mInputManager;
+ @Inject SetupUtils mSetupUtils;
@Inject UiFlags mUiFlags;
@Override
@@ -97,7 +98,7 @@ public class SystemSetupActivity extends SetupActivity {
params.getString(
SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- Intent intent = CommonUtils.createSetupIntent(input);
+ Intent intent = mSetupUtils.createSetupIntent(this, input);
if (intent == null) {
Toast.makeText(
this,
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
index a0cfad32..8265d178 100644
--- a/src/com/android/tv/ui/SelectInputView.java
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -287,10 +287,18 @@ public class SelectInputView extends VerticalGridView
CharSequence customLabel = input.loadCustomLabel(getContext());
CharSequence label = input.loadLabel(getContext());
if (TextUtils.isEmpty(customLabel) || customLabel.equals(label)) {
- inputLabelView.setText(label);
+ if (input.isPassthroughInput()) {
+ inputLabelView.setText(label);
+ } else {
+ inputLabelView.setText(R.string.input_long_label_for_tuner);
+ }
secondaryInputLabelView.setVisibility(View.GONE);
} else {
- inputLabelView.setText(customLabel);
+ if (input.isPassthroughInput()) {
+ inputLabelView.setText(customLabel);
+ } else {
+ inputLabelView.setText(R.string.input_long_label_for_tuner);
+ }
secondaryInputLabelView.setText(label);
secondaryInputLabelView.setVisibility(View.VISIBLE);
}
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index a736e79d..3ac841c2 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -19,6 +19,7 @@ package com.android.tv.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -28,12 +29,14 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.PlaybackParams;
+import android.media.tv.AitInfo;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.tv.TvView.OnUnhandledInputEventListener;
+import android.media.tv.interactive.TvInteractiveAppView;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
@@ -194,6 +197,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
private final InputSessionManager mInputSessionManager;
private int mChannelSignalStrength;
+ private TvInteractiveAppView mTvIAppView;
private final TvInputCallbackCompat mCallback =
new TvInputCallbackCompat() {
@@ -413,6 +417,25 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
mOnTuneListener.onChannelSignalStrength();
}
}
+
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Override
+ public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+ if (!TvFeatures.HAS_TIAF.isEnabled(getContext())) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG,
+ "onAitInfoUpdated: {inputId="
+ + inputId
+ + ", AitInfo=("
+ + aitInfo
+ +")}");
+ }
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onAitInfoUpdated(inputId, aitInfo);
+ }
+ }
};
public TunableTvView(Context context) {
@@ -476,18 +499,26 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
});
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
}
+ public void initialize(
+ ProgramDataManager programDataManager,
+ TvInputManagerHelper tvInputManagerHelper,
+ LegacyFlags legacyFlags) {
+ initialize(programDataManager, tvInputManagerHelper, legacyFlags, null);
+ }
public void initialize(
ProgramDataManager programDataManager,
TvInputManagerHelper tvInputManagerHelper,
- LegacyFlags mLegacyFlags) {
+ LegacyFlags legacyFlags,
+ TvInteractiveAppView tvIAppView) {
mTvView = findViewById(R.id.tv_view);
- mTvView.setUseSecureSurface(!BuildConfig.ENG && !mLegacyFlags.enableDeveloperFeatures());
+ mTvView.setUseSecureSurface(!BuildConfig.ENG && !legacyFlags.enableDeveloperFeatures());
mProgramDataManager = programDataManager;
mInputManagerHelper = tvInputManagerHelper;
mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
mParentalControlSettings = tvInputManagerHelper.getParentalControlSettings();
+ mTvIAppView = tvIAppView;
if (mInputSessionManager != null) {
mTvViewSession = mInputSessionManager.createTvViewSession(mTvView, this, mCallback);
} else {
@@ -715,6 +746,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
}
}
+ @Override
+ public float getStreamVolume() {
+ return mIsMuted
+ ? 0
+ : mVolume;
+ }
+
/**
* Sets fixed size for the internal {@link android.view.Surface} of {@link
* android.media.tv.TvView}. If either {@code width} or {@code height} is non positive, the
@@ -773,6 +811,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
void onContentAllowed();
void onChannelSignalStrength();
+
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ void onAitInfoUpdated(String inputId, AitInfo aitInfo);
}
public void unblockContent(TvContentRating rating) {
@@ -976,6 +1017,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
return;
}
mBlockScreenView.setVisibility(VISIBLE);
+ if (mTvIAppView != null) {
+ mTvIAppView.setVisibility(INVISIBLE);
+ }
mBlockScreenView.setBackgroundImage(null);
if (blockReason == VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED) {
mBlockScreenView.setIconVisibility(true);
@@ -1007,6 +1051,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV
if (mBlockScreenView.getVisibility() == VISIBLE) {
mBlockScreenView.fadeOut();
}
+ if (mTvIAppView != null) {
+ mTvIAppView.setVisibility(VISIBLE);
+ }
}
}
diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java
index cf1a9113..19af23b9 100644
--- a/src/com/android/tv/ui/TvOverlayManager.java
+++ b/src/com/android/tv/ui/TvOverlayManager.java
@@ -55,6 +55,7 @@ import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.RecentlyWatchedDialogFragment;
import com.android.tv.dialog.SafeDismissDialogFragment;
+import com.android.tv.dialog.InteractiveAppDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.guide.ProgramGuide;
@@ -198,6 +199,7 @@ public class TvOverlayManager implements AccessibilityStateChangeListener {
AVAILABLE_DIALOG_TAGS.add(LicenseDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(InteractiveAppDialogFragment.DIALOG_TAG);
}
private final MainActivity mMainActivity;
diff --git a/src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java b/src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java
new file mode 100755
index 00000000..b56a1d66
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.ui.sidepanel;
+
+import com.android.tv.R;
+import com.android.tv.util.TvSettings;
+import java.util.ArrayList;
+import java.util.List;
+
+public class InteractiveAppSettingsFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "Interactive Application Settings";
+ @Override
+ protected String getTitle() {
+ return getString(R.string.interactive_app_settings);
+ }
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+ items.add(
+ new SwitchItem(
+ getString(R.string.tv_iapp_on),
+ getString(R.string.tv_iapp_off)) {
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(TvSettings.isTvIAppOn(getContext()));
+ }
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ boolean checked = isChecked();
+ TvSettings.setTvIAppOn(getContext(), checked);
+ }
+ });
+ return items;
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
index 1c03b6a9..762a190c 100644
--- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
@@ -29,6 +29,7 @@ import com.android.tv.common.CommonPreferences;
import com.android.tv.common.customization.CustomizationManager;
import com.android.tv.common.util.PermissionUtils;
import com.android.tv.dialog.PinDialogFragment;
+import com.android.tv.features.TvFeatures;
import com.android.tv.license.LicenseSideFragment;
import com.android.tv.license.Licenses;
import com.android.tv.util.Utils;
@@ -190,6 +191,22 @@ public class SettingsFragment extends SideFragment {
}
});
}
+
+ //Interactive Application Settings
+ if (TvFeatures.HAS_TIAF.isEnabled(getContext()))
+ {
+ items.add(
+ new ActionItem(getString(R.string.interactive_app_settings)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
+ .show(new InteractiveAppSettingsFragment(), false);
+ }
+ });
+ }
+
// Show version.
SimpleActionItem version =
new SimpleActionItem(
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
index 52b3e3e8..aaee1047 100644
--- a/src/com/android/tv/util/SetupUtils.java
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -31,14 +31,18 @@ import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.common.singletons.HasTvInputId;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.api.Channel;
import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
import com.google.common.base.Optional;
+
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -362,6 +366,52 @@ public class SetupUtils {
}
/**
+ * Create a Intent to launch setup activity for {@code inputId}. The setup activity defined
+ * in the overlayable resources precedes the one defined in the corresponding TV input service.
+ */
+ @Nullable
+ public Intent createSetupIntent(Context context, TvInputInfo input) {
+ String[] componentStrings = context.getResources()
+ .getStringArray(R.array.setup_ComponentNames);
+
+ if (componentStrings != null) {
+ for (String component : componentStrings) {
+ String[] split = component.split("#");
+ if (split.length != 2) {
+ Log.w(TAG, "Invalid component item: " + Arrays.toString(split));
+ continue;
+ }
+
+ final String inputId = split[0].trim();
+ if (inputId.equals(input.getId())) {
+ final String flattenedComponentName = split[1].trim();
+ final ComponentName componentName = ComponentName
+ .unflattenFromString(flattenedComponentName);
+ if (componentName == null) {
+ Log.w(TAG, "Failed to unflatten component: " + flattenedComponentName);
+ continue;
+ }
+
+ final Intent overlaySetupIntent = new Intent(Intent.ACTION_MAIN);
+ overlaySetupIntent.setComponent(componentName);
+ overlaySetupIntent.putExtra(TvInputInfo.EXTRA_INPUT_ID, inputId);
+
+ PackageManager pm = context.getPackageManager();
+ if (overlaySetupIntent.resolveActivityInfo(pm, 0) == null) {
+ Log.w(TAG, "unable to find component" + flattenedComponentName);
+ continue;
+ }
+
+ Log.i(TAG, "overlay input id: " + inputId
+ + " to setup activity: " + flattenedComponentName);
+ return CommonUtils.createSetupIntent(overlaySetupIntent, inputId);
+ }
+ }
+ }
+ return CommonUtils.createSetupIntent(input);
+ }
+
+ /**
* Called when an setup is done. Once it is called, {@link #isSetupDone} returns {@code true}
* for {@code inputId}.
*/
diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java
index ae79e7e5..1a5434cb 100644
--- a/src/com/android/tv/util/TvSettings.java
+++ b/src/com/android/tv/util/TvSettings.java
@@ -53,6 +53,9 @@ public final class TvSettings {
private static final String PREF_CONTENT_RATING_LEVEL = "pref.content_rating_level";
private static final String PREF_DISABLE_PIN_UNTIL = "pref.disable_pin_until";
+ // tviapp settings
+ private static final String PREF_TV_IAPP_STATES = "pref.tviapp_on";
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({
CONTENT_RATING_LEVEL_NONE,
@@ -242,4 +245,16 @@ public final class TvSettings {
.putLong(PREF_DISABLE_PIN_UNTIL, timeMillis)
.apply();
}
+
+ public static boolean isTvIAppOn(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(PREF_TV_IAPP_STATES, false);
+ }
+
+ public static void setTvIAppOn(Context context, boolean isOn) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putBoolean(PREF_TV_IAPP_STATES, isOn)
+ .apply();
+ }
}