/*
* Copyright (C) 2015 Google Inc. All Rights Reserved.
*
* 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.google.android.libraries.cast.companionlibrary.cast;
import com.google.android.gms.cast.LaunchOptions;
import com.google.android.libraries.cast.companionlibrary.utils.Utils;
import android.app.Service;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v7.app.MediaRouteDialogFactory;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* A class that enables clients to customize certain configuration parameters for this library.
* Clients need to use {@link Builder} to build an instance of this class and pass that to the
* {@link VideoCastManager#initialize(Context, CastConfiguration)} or
* {@link DataCastManager#initialize(Context, CastConfiguration)}.
*/
public class CastConfiguration {
public static final int NOTIFICATION_ACTION_PLAY_PAUSE = 1;
public static final int NOTIFICATION_ACTION_SKIP_NEXT = 2;
public static final int NOTIFICATION_ACTION_SKIP_PREVIOUS = 3;
public static final int NOTIFICATION_ACTION_DISCONNECT = 4;
public static final int NOTIFICATION_ACTION_REWIND = 5;
public static final int NOTIFICATION_ACTION_FORWARD = 6;
/**
* Available built-in actions for adding to cast notifications
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({NOTIFICATION_ACTION_PLAY_PAUSE, NOTIFICATION_ACTION_SKIP_NEXT,
NOTIFICATION_ACTION_SKIP_PREVIOUS, NOTIFICATION_ACTION_DISCONNECT,
NOTIFICATION_ACTION_REWIND, NOTIFICATION_ACTION_FORWARD})
@interface NotificationAction {}
public static final int FEATURE_DEBUGGING = 1;
public static final int FEATURE_LOCKSCREEN = 1 << 1;
public static final int FEATURE_NOTIFICATION = 1 << 2;
public static final int FEATURE_WIFI_RECONNECT = 1 << 3;
public static final int FEATURE_CAPTIONS_PREFERENCE = 1 << 4;
public static final int FEATURE_AUTO_RECONNECT = 1 << 5;
public static final int NEXT_PREV_VISIBILITY_POLICY_HIDDEN = 1;
public static final int NEXT_PREV_VISIBILITY_POLICY_DISABLED = 2;
public static final int NEXT_PREV_VISIBILITY_POLICY_ALWAYS = 3;
/**
* Constants representing various policies that can be enforced for the visibility of the
* "Skip Next" or "Skip Previous" buttons used in navigating to the next or previous queue item
* in the {@link
* com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity}
* <ul>
* <li>{@code NEXT_PREV_VISIBILITY_POLICY_HIDDEN}: buttons should be hidden when there is
* no more queue item in the corresponding direction.
* <li>{@code NEXT_PREV_VISIBILITY_POLICY_DISABLED}: buttons should be visible but disabled
* when there is no more queue item in the corresponding direction.
* <li>{@code NEXT_PREV_VISIBILITY_POLICY_ALWAYS}: buttons should remain visible and
* enabled all the time.
* </ul>
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({NEXT_PREV_VISIBILITY_POLICY_HIDDEN, NEXT_PREV_VISIBILITY_POLICY_DISABLED,
NEXT_PREV_VISIBILITY_POLICY_ALWAYS})
public @interface PrevNextPolicy {}
public static final int CCL_DEFAULT_FORWARD_STEP_S = 30;
private static final int NOTIFICATION_MAX_EXPANDED_ACTIONS = 5;
private static final int NOTIFICATION_MAX_COMPACT_ACTIONS = 3;
private List<Integer> mNotificationActions;
private List<Integer> mNotificationCompactActions;
private int mNextPrevVisibilityPolicy;
private int mCapabilities;
private String mApplicationId;
private Class<?> mTargetActivity;
private Class<? extends Service> mCustomNotificationService;
private List<String> mNamespaces;
private LaunchOptions mLaunchOptions;
private boolean mCastControllerImmersive;
private int mForwardStep;
private MediaRouteDialogFactory mMediaRouteDialogFactory;
private final boolean mDisableLaunchOnConnect;
private CastConfiguration(Builder builder) {
if (builder.mDebugEnabled) {
mCapabilities |= FEATURE_DEBUGGING;
}
if (builder.mLockScreenEnabled) {
mCapabilities |= FEATURE_LOCKSCREEN;
}
if (builder.mNotificationEnabled) {
mCapabilities |= FEATURE_NOTIFICATION;
}
if (builder.mWifiReconnectEnabled) {
mCapabilities |= FEATURE_WIFI_RECONNECT;
}
if (builder.mCaptionPreferenceEnabled) {
mCapabilities |= FEATURE_CAPTIONS_PREFERENCE;
}
if (builder.mAutoReconnectEnabled) {
mCapabilities |= FEATURE_AUTO_RECONNECT;
}
mNotificationActions = new ArrayList<>(builder.mNotificationActions);
mNotificationCompactActions = new ArrayList<>(builder.mNotificationCompactActions);
mNextPrevVisibilityPolicy = builder.mNextPrevVisibilityPolicy;
mApplicationId = builder.mApplicationId;
mTargetActivity = builder.mTargetActivity;
if (!builder.mNamespaces.isEmpty()) {
mNamespaces = new ArrayList<>(builder.mNamespaces);
}
if (builder.mLocale != null) {
mLaunchOptions = new LaunchOptions.Builder().setLocale(builder.mLocale)
.setRelaunchIfRunning(builder.mRelaunchIfRunning).build();
} else {
mLaunchOptions = new LaunchOptions.Builder().setRelaunchIfRunning(false).build();
}
mCastControllerImmersive = builder.mCastControllerImmersive;
mForwardStep = builder.mForwardStep;
mCustomNotificationService = builder.mCustomNotificationService;
mMediaRouteDialogFactory = builder.mMediaRouteDialogFactory;
mDisableLaunchOnConnect = builder.mDisableLaunchOnConnect;
}
public List<Integer> getNotificationActions() {
return mNotificationActions;
}
public List<Integer> getNotificationCompactActions() {
return mNotificationCompactActions;
}
public int getCapabilities() {
return mCapabilities;
}
@PrevNextPolicy
public int getNextPrevVisibilityPolicy() {
return mNextPrevVisibilityPolicy;
}
public String getApplicationId() {
return mApplicationId;
}
public Class<?> getTargetActivity() {
return mTargetActivity;
}
public List<String> getNamespaces() {
return mNamespaces;
}
public LaunchOptions getLaunchOptions() {
return mLaunchOptions;
}
public boolean isCastControllerImmersive() {
return mCastControllerImmersive;
}
public boolean isDisableLaunchOnConnect() {
return mDisableLaunchOnConnect;
}
public int getForwardStep() {
return mForwardStep;
}
public Class<? extends Service> getCustomNotificationService() {
return mCustomNotificationService;
}
public MediaRouteDialogFactory getMediaRouteDialogFactory() {
return mMediaRouteDialogFactory;
}
/**
* Builder for instantiating the {@link CastConfiguration}.
*/
public static class Builder {
private List<Integer> mNotificationActions;
private List<Integer> mNotificationCompactActions;
private boolean mDebugEnabled;
private boolean mLockScreenEnabled;
private boolean mNotificationEnabled;
private boolean mWifiReconnectEnabled;
private boolean mCaptionPreferenceEnabled;
private boolean mAutoReconnectEnabled;
private int mNextPrevVisibilityPolicy = NEXT_PREV_VISIBILITY_POLICY_DISABLED;
private String mApplicationId;
private Class<?> mTargetActivity;
private List<String> mNamespaces;
private boolean mRelaunchIfRunning;
private Locale mLocale;
private boolean mCastControllerImmersive = true;
private int mForwardStep = CCL_DEFAULT_FORWARD_STEP_S;
private Class<? extends Service> mCustomNotificationService;
private MediaRouteDialogFactory mMediaRouteDialogFactory;
private boolean mDisableLaunchOnConnect;
public Builder(String applicationId) {
mApplicationId = Utils.assertNotEmpty(applicationId, "applicationId");
mNotificationActions = new ArrayList<>();
mNotificationCompactActions = new ArrayList<>();
mNamespaces = new ArrayList<>();
}
public CastConfiguration build() {
if (!mNotificationEnabled && !mNotificationActions.isEmpty()) {
throw new IllegalArgumentException(
"Notification was not enabled but some notification actions were "
+ "configured");
}
if (mNotificationActions.size() > NOTIFICATION_MAX_EXPANDED_ACTIONS) {
throw new IllegalArgumentException(
"You cannot add more than " + NOTIFICATION_MAX_EXPANDED_ACTIONS
+ " notification actions for the expanded view");
}
if (mNotificationCompactActions.size() > NOTIFICATION_MAX_COMPACT_ACTIONS) {
throw new IllegalArgumentException(
"You cannot add more than " + NOTIFICATION_MAX_COMPACT_ACTIONS
+ " compact notification actions for the compact view");
}
if (mCustomNotificationService != null && !mNotificationEnabled) {
throw new IllegalArgumentException("For custom notifications, you should enable "
+ "notifications first");
}
return new CastConfiguration(this);
}
/**
* Enables debugging in Play Services
*/
public Builder enableDebug() {
mDebugEnabled = true;
return this;
}
/**
* Enables lock screen controllers
*/
public Builder enableLockScreen() {
mLockScreenEnabled = true;
return this;
}
/**
* Prevents the automatic launch of the app when a connection to the cast device has been
* established.
*/
public Builder disableLaunchOnConnect() {
mDisableLaunchOnConnect = true;
return this;
}
/**
* Enables notifications. If you enable this feature, you would most likely want to add
* one or more notification actions.
*
* @see #addNotificationAction(int, boolean)
*/
public Builder enableNotification() {
mNotificationEnabled = true;
return this;
}
/**
* Enable reconnection attempt when wifi connectivity is re-established
*/
public Builder enableWifiReconnection() {
mWifiReconnectEnabled = true;
return this;
}
/**
* Enable caption management. Enabling this feature provides a preference page for
* configuring closed captions across all versions of android.
*/
public Builder enableCaptionManagement() {
mCaptionPreferenceEnabled = true;
return this;
}
/**
* Enables auto-reconnection.
*/
public Builder enableAutoReconnect() {
mAutoReconnectEnabled = true;
return this;
}
/**
* Adds a notification action to the MediaStyle notification. To use this, you have to
* first enable the notifications feature by calling {@link #enableNotification()}. Note
* that you cannot have more than 5 actions.
*
* @param actionType Type of action to be added. It can be one of the predefined actions:
* <ul>
* <li>{@link #NOTIFICATION_ACTION_PLAY_PAUSE}
* <li>{@link #NOTIFICATION_ACTION_SKIP_NEXT}
* <li>{@link #NOTIFICATION_ACTION_SKIP_PREVIOUS}
* <li>{@link #NOTIFICATION_ACTION_FORWARD}
* <li>{@link #NOTIFICATION_ACTION_REWIND}
* <li>{@link #NOTIFICATION_ACTION_DISCONNECT}
* </ul>.
* @param showInCompact If {@code true}, this action will be shown in the compact view as
* well. Note that there can't be more than three actions in the compact view.
*/
public Builder addNotificationAction(@NotificationAction int actionType,
boolean showInCompact) {
if (!mNotificationActions.contains(actionType)) {
if (showInCompact) {
mNotificationCompactActions.add(mNotificationActions.size());
}
mNotificationActions.add(actionType);
}
return this;
}
/**
* Sets the "Target Activity". If called, this will replace the default
* {@link com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity}
* that the library provides and will be invoked if user taps on the notification content or
* mini-controller. If you use this method to define a custom activity, be aware that the
* {@link VideoCastManager#startVideoCastControllerActivity(Context, Bundle, int, boolean)}
* (or other variations of that method) will not start your custom activity.
*/
public Builder setTargetActivity(@NonNull Class<?> targetActivity) {
mTargetActivity = Utils.assertNotNull(targetActivity, "targetActivity");
return this;
}
/**
* (Optional) Sets the policy for the visibility/status of the Skip Next/Prev buttons in
* the
* {@link com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity}.
* The policy declares what should the visibility or status of these buttons be when the
* position of the current item is at the edges of the queue. For example, if the current
* item is the last item in the queue, what should be the visibility or status of the
* "Skip Next" button. Available policies are:
* <ul>
* <li>{@link CastConfiguration#NEXT_PREV_VISIBILITY_POLICY_ALWAYS}: always show the
* button
* <li>{@link CastConfiguration#NEXT_PREV_VISIBILITY_POLICY_DISABLED}: disable the button
* <li>{@link CastConfiguration#NEXT_PREV_VISIBILITY_POLICY_HIDDEN}: hide the button
* </ul>
* The default behavior is {@link CastConfiguration#NEXT_PREV_VISIBILITY_POLICY_DISABLED}
*/
public Builder setNextPrevVisibilityPolicy(@PrevNextPolicy int policy) {
mNextPrevVisibilityPolicy = policy;
return this;
}
/**
* (Optional) Adds a namespace so that the library can set up a custom channel based on
* that
* name. Note that for {@link VideoCastManager}, at most one namespace can be added but
* that
* restriction doesn't exist for {@link DataCastManager}
*/
public Builder addNamespace(@NonNull String namespace) {
mNamespaces.add(Utils.assertNotEmpty(namespace, "namespace"));
return this;
}
/**
* (Optional) Sets launch options. If you are calling this method, you need to provide a
* non-null {@link Locale} as well.
*/
public Builder setLaunchOptions(boolean relaunchIfRunning, @NonNull Locale locale) {
mLocale = Utils.assertNotNull(locale, "locale");
mRelaunchIfRunning = relaunchIfRunning;
return this;
}
/**
* (Optional) Sets whether the
* {@link com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity}
* should be shown in immersive mode or not. The default behavior is immersive.
*/
public Builder setCastControllerImmersive(boolean isImmersive) {
mCastControllerImmersive = isImmersive;
return this;
}
/**
* Sets the amount to jump if {@link #NOTIFICATION_ACTION_FORWARD} or
* {@link #NOTIFICATION_ACTION_REWIND} are included for the notification actions. Any tap
* on those actions will result in moving the media position forward or backward by
* {@code lengthInSeconds} seconds; positive values will move the position forward and
* negative values rewind the position..
*/
public Builder setForwardStep(int lengthInSeconds) {
mForwardStep = lengthInSeconds;
return this;
}
/**
* (Optional) Sets a custom notification service, to be controlled by the library. Clients
* can either write their own service, or they can extend
* {@link com.google.android.libraries.cast.companionlibrary.notification.VideoCastNotificationService}.
* CCL controls the lifecycle of this service by calling
* {@link VideoCastManager#startNotificationService()} and
* {@link VideoCastManager#stopNotificationService()}. It also notifies this service when
* the notification should become visible or hidden.
*
* @see VideoCastManager#startNotificationService()
* @see VideoCastManager#stopNotificationService()
*/
public Builder setCustomNotificationService(
Class<? extends Service> customNotificationService) {
mCustomNotificationService = Utils
.assertNotNull(customNotificationService, "customNotificationService");
return this;
}
/**
* (Optional) Sets the {@link MediaRouteDialogFactory}. This is optional and if not called,
* the default dialog will be used.
*/
public Builder setMediaRouteDialogFactory(MediaRouteDialogFactory factory) {
mMediaRouteDialogFactory = factory;
return this;
}
}
}