# Migrate CCL Sender App to the Cast Application Framework (CAF)

The following procedure enables you to convert your Android sender app from
Cast SDK v2 with CCL to CAF. All of the functionality of CCL has been
implemented in CAF, so once you migrate, you will no longer need to use CCL.

> [!WARNING]
> **Warning**: If you're building a CAF sender app or migrating to CAF, you should be using CastContext and not GoogleAPIClient for accessing the Cast API. It is proper to use GoogleAPIClient for accessing other Google Play services APIs.

The Cast CAF Sender SDK uses CastContext to manage the GoogleAPIClient on your behalf.
CastContext manages lifecycles, errors and callbacks for you, which greatly
simplifies developing a Cast app.

## Introduction

- Because the CAF Sender design was influenced by Cast Companion Library, the migration from CCL to CAF Sender involves mostly one-to-one mappings of classes and their methods.
- CAF Sender is still distributed as part of Google Play services using the Android SDK manager.
- New packages (`com.google.android.gms.cast.framework.*`) that have been added to CAF Sender, with functionality similar to CCL, take on responsibility for complying with the [Google Cast Design checklist](https://developers.google.com/cast/docs/design_checklist).
- CAF Sender provides widgets that comply with the Cast UX requirements; these widgets are similar to those provided by CCL.
- CAF Sender provides asynchronous callbacks that are similar to CCL, to track states and obtain data. Unlike CCL, CAF Sender does not provide any no-op implementations of the various interface methods.

In the following sections, we will mainly focus on the video­centric
applications based on CCL's VideoCastManager, but in many cases, the same
concepts apply to DataCastManager as well.

## Dependencies

CCL and CAF have the same dependencies on the AppCompat support library,
MediaRouter v7 support library and Google Play services. However, the difference
is that CAF depends on the new Cast framework that is available in Google Play
services 9.2.0 or later.

In your build.gradle file, remove the dependencies on
`com.google.android.gms:play-services-cast` and
`com.google.android.libraries.cast.companionlibrary:ccl`,
then add the new Cast framework:

    dependencies {
        compile 'com.android.support:appcompat-v7:23.4.0'
        compile 'com.android.support:mediarouter-v7:23.4.0'
        compile 'com.google.android.gms:play-services-cast-framework:9.4.0'
    }

You can also remove the Google Play service metadata:

    <meta‐data android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version"/>

Any services, activities, and resources that are part of CAF are automatically
merged with your app's manifest and resources.

The minimum Android SDK version that CAF supports is 9
(Gingerbread); CCL's minimum Android SDK version is 10.

CCL provides a convenience method,
`BaseCastManager.checkGooglePlayServices(activity)`, to verify that a compatible
version of the Google Play services is available on the device. CAF does not
provide this as part of the Cast SDK. Follow the procedure
[Ensure Devices Have the Google Play services APK](https://developers.google.com/android/guides/setup#ensure_devices_have_the_google_play_services_apk)
to ensure that the correct Google Play services APK is installed on a user's
device since updates might not reach all users immediately.

You are still required to use a variant of Theme.AppCompat for the application's
theme.

## Initialization

For CCL, `VideoCastManager.initialize()` was required to be called in the
`onCreate()` method of the Applications instance. This logic should be
removed from your Application class code.

In CAF, an explicit initialization step is also required for the Cast
framework. This involves initializing the `CastContext` singleton, using an
appropriate `OptionsProvider` to specify the receiver application ID and any
other global options. The `CastContext` plays a similar role to CCL's
`VideoCastManager` by providing a singleton that clients interact with.
The `OptionsProvider` is similar to CCL's `CastConfiguration` to allow you
to configure the Cast framework features.

If your current CCL `CastConfiguration.Builder` looks like this:

    VideoCastManager.initialize(
       getApplicationContext(),
       new CastConfiguration.Builder(context.getString(R.string.app_id))
           .enableWifiReconnection()
           .enableAutoReconnect()
           .build());

Then in CAF the following `CastOptionsProvider` using the `CastOptions.Builder`
would be similar:

    public class CastOptionsProvider implements OptionsProvider {

        @Override
        public CastOptions getCastOptions(Context context) {
            return new CastOptions.Builder()
                    .setReceiverApplicationId(context.getString(R.string.app_id))
                    .build();
        }

        @Override
        public List<SessionProvider> getAdditionalSessionProviders(
                Context context) {
            return null;
        }
    }

Take a look at our [sample app](https://github.com/googlecast/CastVideos-android/blob/master/src/com/google/sample/cast/refplayer/CastOptionsProvider.java)
for a complete implementation of the OptionsProvider.

Declare the OptionsProvider within the "application" element of the
AndroidManifest.xml file:

    <application>
    ...
        <meta-data
            android:name=
              "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
            android:value="com.google.sample.cast.refplayer.CastOptionsProvider"    
        />
    </application>

Lazily initialize the `CastContext` in each `Activity`'s `onCreate` method
(and not the `Application` instance):

    private CastContext mCastContext;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.video_browser);
        setupActionBar();

        mCastContext = CastContext.getSharedInstance(this);
    }

To access the `CastContext` singleton use:

    mCastContext = CastContext.getSharedInstance(this);

## Device discovery

CCL's `VideoCastManager` `incrementUiCounter` and `decrementUiCounter` should
be removed from the `onResume` and `onPause` methods of your `Activities`.

In CAF, the discovery process is started and stopped automatically by the
framework when the app comes to the foreground and goes to the background,
respectively.

> [!NOTE]
> **Note:** Gingerbread lacks certain methods used by the framework to perform automatic discovery. If the sender app needs to support Gingerbread, then it needs to call `CastContext` method `registerLifecycleCallbacksBeforeIceCreamSandwich()` in the `onCreate()` method for each and every one of its `Activities`, so that the framework knows when to start and stop discovery.

## Cast button and Cast dialog

As with CCL, these components are provided by the MediaRouter v7 support
library.

The Cast button is still implemented by the `MediaRouteButton` and can be added
to your activity (using either an `ActionBar` or a `Toolbar`), as a menu item
in your menu.

The declaration of the `MediaRouteActionProvider` in the menu xml is the same as
with CCL:

    <item
        android:id="@+id/media_route_menu_item"
        android:title="@string/media_route_menu_title"
        app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
        app:showAsAction="always"/>

Similar to CCL, override the onCreateOptionMenu() method of each Activity, but
instead of using CastManager.addMediaRouterButton, use CAF's CastButtonFactory
to wire up the MediaRouteButton to the Cast framework:

    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.browse, menu);
        CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                                    menu,
                                                    R.id.media_route_menu_item);
        return true;
    }

> [!NOTE]
> **Note:** CAF does not allow you to specify your own `MediaRouteDialogFactory` to override the layout and styling of the default `MediaRouter` dialogs.

## Device control

Similar to CCL, in CAF, device control is largely handled by the framework.
The sender application does not need to handle (and should not try to handle)
connecting to the device and launching the receiver application using
`GoogleApiClient`.

Interaction between sender and receiver is now represented as a "session". The
`SessionManager` class handles the session lifecycle and automatically starts
and stops sessions in response to user gestures: a session is started when the
user selects a Cast device in the Cast dialog and is ended when the user taps
the "Stop Casting" button in the Cast dialog or when the sender app itself
terminates.

In CCL you have to extend the `VideoCastConsumerImpl` class to track the cast
session status:

    private final VideoCastConsumer mCastConsumer = new VideoCastConsumerImpl() {
      public void onApplicationConnected(ApplicationMetadata appMetadata, 
                                         String sessionId,
                                         boolean wasLaunched) {}
      public void onDisconnectionReason(int reason) {}
      public void onDisconnected() {}
    }

In CAF, the sender application can be notified of session lifecycle events by
registering a `SessionManagerListener` with the `SessionManager`. The
SessionManagerListener callbacks define callback methods for all session
lifecycle events.

The following `SessionManagerListener` methods are mapped from CCL's
`VideoCastConsumer` interface:

- `VideoCastConsumer.onApplicationConnected` -\> `SessionManagerListener.onSessionStarted`
- `VideoCastConsumer.onDisconnected` -\> `SessionManagerListener.onSessionEnded`

Declare a class that implements the `SessionManagerListener` interface and move
the `VideoCastConsumerImpl` logic to the matching methods:

    private class CastSessionManagerListener implements SessionManagerListener<CastSession> {
      public void onSessionEnded(CastSession session, int error) {}
      public void onSessionStarted(CastSession session, String sessionId) {}
      public void onSessionEnding(CastSession session) {}
      ...
    }

The `CastSession` class represents a session with a Cast device. The class has
methods for controlling the device volume and mute states, which CCL does in the
`BaseCastManager`.

Instead of using the CCL `VideoCastManager` to add a consumer:

    VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);

Now register your `SessionManagerListener`:

    mCastSessionManager = 
        CastContext.getSharedInstance(this).getSessionManager();
    mCastSessionManagerListener = new CastSessionManagerListener();
    mCastSessionManager.addSessionManagerListener(mCastSessionManagerListener,
                      CastSession.class);

To stop listening to events in CCL:

    VideoCastManager.getInstance().removeVideoCastConsumer(mCastConsumer);

Now use the `SessionManager` to stop listening to session events:

    mCastSessionManager.removeSessionManagerListener(mCastSessionManagerListener,
                        CastSession.class);

To explicitly disconnect from the Cast device, CCL used:

    VideoCastManager.disconnectDevice(boolean stopAppOnExit, 
                boolean clearPersistedConnectionData,
                boolean setDefaultRoute)

For CAF, use the `SessionManager`:

    CastContext.getSharedInstance(this).getSessionManager()
                                       .endCurrentSession(true);

To determine if the sender is connected to the receiver, CCL provides
`VideoCastManager.getInstance().isConnected()`, but in CAF use the
`SessionManager`:

    public boolean isConnected() {
        CastSession castSession = CastContext.getSharedInstance(mAppContext)
                                      .getSessionManager()
                                      .getCurrentCastSession();
        return (castSession != null && castSession.isConnected());
    }

In CAF, volume/mute state change notifications are still delivered via callback
methods in the `Cast.Listener`; these listeners are registered with
`CastSession`. All of the remaining device state notifications are delivered via
`CastStateListener` callbacks; these listeners are registered with the
`CastSession`. Make sure you still unregister listeners when the associated
fragments, activities or apps go to the background.

## Reconnection logic

CAF attempts to re-establish network connections that are lost due
to temporary WiFi signal loss or other network errors. This is now done at the
session level; a session can enter a "suspended" state when the connection is
lost, and will transition back to a "connected" state when connectivity is
restored. The framework takes care of reconnecting to the receiver application
and reconnecting any Cast channels as part of this process.

CAF provides its own reconnection service, so you can remove
the CCL `ReconnectionService` from your manifest:

    <service android:name="com.google.android.libraries.cast.companionlibrary.cast.reconnection.ReconnectionService"/>

You also don't need the following permissions in your manifest for the
reconnection logic:

    <uses‐permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses‐permission android:name="android.permission.ACCESS_WIFI_STATE"/>

The CAF reconnection service is enabled by default but can be disabled using the
`CastOptions`.

In addition, CAF also adds automatic session resumption which is enabled by
default (and can be deactivated via `CastOptions`). If the sender application is
sent to the background or is terminated (by swiping away or because of a crash)
while a Cast session is in progress, the framework will attempt to resume that
session when the sender application returns to the foreground or is relaunched;
this is handled automatically by the `SessionManager`, which will issue the
appropriate callbacks on any registered `SessionManagerListener` instances.

## Custom channel registration

CCL provides two ways to create a custom message channel to the receiver:

- `CastConfiguration` allows you to specify multiple namespaces and CCL will then create the channel for you.
- `DataCastManager` is similar to VideoCastManager but focused on non-media use cases.

Neither of these ways of creating a custom channel is supported by CAF -- you
have to instead follow the procedure
[Add a Custom Channel](https://developers.google.com/cast/docs/android_sender_advanced#add_a_custom_channel)
for your sender app.

Similar to CCL, for media applications, it is not necessary to explicitly
register the media control channel.

## Media control

In CAF, the `RemoteMediaClient` class is equivalent to the `VideoCastManager`
media methods. The `RemoteMediaClient.Listener` is equivalent to
`VideoCastConsumer` methods. In particular, the
`onRemoteMediaPlayerMetadataUpdated` and `onRemoteMediaPlayerStatusUpdated`
methods of `VideoCastConsumer` maps to the `onMetadataUpdated` and
`onStatusUpdated` methods of `RemoteMediaClient.Listener` respectively:

    private class CastMediaClientListener implements RemoteMediaClient.Listener {

        @Override
        public void onMetadataUpdated() {
            setMetadataFromRemote();
        }

        @Override
        public void onStatusUpdated() {
            updatePlaybackState();
        }

        @Override
        public void onSendingRemoteMediaRequest() {
        }

        @Override
        public void onQueueStatusUpdated() {
        }

        @Override
        public void onPreloadStatusUpdated() {
        }
    }

It is not necessary to explicitly initialize or register the `RemoteMediaClient`
object; the framework will automatically instantiate the object and register the
underlying media channel at session start time if the receiver application being
connected to supports the media namespace.

The `RemoteMediaClient` can be accessed as the `getRemoteMediaClient` method of
the `CastSession` object.

    CastSession castSession = CastContext.getSharedInstance(mAppContext)
                                         .getSessionManager()
                                         .getCurrentCastSession();
    mRemoteMediaClient = castSession.getRemoteMediaClient();
    mRemoteMediaClientListener = new CastMediaClientListener();

Instead of CCL's:

    VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);

Now use CAF:

    mRemoteMediaClient.addListener(mRemoteMediaClientListener);

Any number of listeners can be registered with the `RemoteMediaClient`,
which allows multiple sender components to share the single instance of
`RemoteMediaClient` that is associated with the session.

CCL's `VideoCastManager` provides methods to handle media playback:

    VideoCastManager manager = VideoCastManager.getInstance();
    if (manager.isRemoteMediaLoaded()) {
        manager.pause();
        mCurrentPosition = (int) manager.getCurrentMediaPosition();
    }

These are now implemented by RemoteMediaClient in CAF:

    if (mRemoteMediaClient.hasMediaSession()) {
        mRemoteMediaClient.pause();
        mCurrentPosition = 
            (int)mRemoteMediaClient.getApproximateStreamPosition();
    }

In CAF, all media requests issued on the `RemoteMediaClient` return a
`RemoteMediaClient.MediaChannelResult` via a `PendingResult` callback
which can be used to track the progress and eventual outcome of the request.

Both CCL and CAF use the `MediaInfo` and `MediaMetadata` classes to represent
media items and to load media.

To load media in CCL, the `VideoCastManager` is used:

    VideoCastManager.getInstance().loadMedia(media, autoPlay, mCurrentPosition, customData);

In CAF, the `RemoteMediaClient` is used to load the media:

    mRemoteMediaClient.load(media, autoPlay, mCurrentPosition, customData);

To get the `Media` information and status of a current media session on the
receiver, CCL uses the `VideoCastManager`:

    MediaInfo mediaInfo = VideoCastManager.getInstance()
                                          .getRemoteMediaInformation();
    int status = VideoCastManager.getInstance().getPlaybackStatus();
    int idleReason = VideoCastManager.getInstance().getIdleReason();

In CAF, use the `RemoteMediaClient` to get the same information:

    MediaInfo mediaInfo = mRemoteMediaClient.getMediaInfo();
    int status = mRemoteMediaClient.getPlayerState();
    int idleReason = mRemoteMediaClient.getIdleReason();

## Introductory overlay

Similar to CCL, CAF provides a custom view `IntroductoryOverlay` to highlight
the Cast button when it is first shown to users.

Instead of using CCL's `VideoCastConsumer` `onCastAvailabilityChanged` method
to know when to display the overlay, declare a `CastStateListener` to determine
when the Cast button becomes visible once Cast devices are discovered on the
local network by the `MediaRouter`:

    private IntroductoryOverlay mIntroductoryOverlay;
    private MenuItem mMediaRouteMenuItem;

    protected void onCreate(Bundle savedInstanceState) {
        ...
        mCastStateListener = new CastStateListener() {
            @Override
            public void onCastStateChanged(int newState) {
                if (newState != CastState.NO_DEVICES_AVAILABLE) {
                    showIntroductoryOverlay();
                }
            }
        };
        mCastContext = CastContext.getSharedInstance(this);
        mCastContext.registerLifecycleCallbacksBeforeIceCreamSandwich(this, 
            savedInstanceState);
    }

    protected void onResume() {
        mCastContext.addCastStateListener(mCastStateListener);
        ...
    }

    protected void onPause() {
        mCastContext.removeCastStateListener(mCastStateListener);
        ...
    }

Keep track of the `MediaRouteMenuItem` instance:

    public boolean onCreateOptionsMenu(Menu menu) {
       super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.browse, menu);
        mMediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(
                getApplicationContext(), menu,
                R.id.media_route_menu_item);
        showIntroductoryOverlay();
        return true;
    }

Check if the `MediaRouteButton` is visible so the introductory overlay
can be shown:

    private void showIntroductoryOverlay() {
        if (mIntroductoryOverlay != null) {
            mIntroductoryOverlay.remove();
        }
        if ((mMediaRouteMenuItem != null) && mMediaRouteMenuItem.isVisible()) {
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    mIntroductoryOverlay = new IntroductoryOverlay.Builder(
                            VideoBrowserActivity.this, mMediaRouteMenuItem)
                            .setTitleText(getString(R.string.introducing_cast))
                            .setOverlayColor(R.color.primary)
                            .setSingleTime()
                            .setOnOverlayDismissedListener(
                                    new IntroductoryOverlay
                                        .OnOverlayDismissedListener() {
                                            @Override
                                            public void onOverlayDismissed() {
                                                mIntroductoryOverlay = null;
                                            }
                                    })
                            .build();
                    mIntroductoryOverlay.show();
                }
            });
        }
    }

Take a look at our
[sample app](https://github.com/googlecast/CastVideos-android/blob/master/src/com/google/sample/cast/refplayer/VideoBrowserActivity.java)
for the complete working code for showing the introductory overlay.

To customize the styling of the introductory overlay, follow the procedure
[Customize Introductory Overlay](https://developers.google.com/cast/docs/android_sender_advanced#customize_introductory_overlay).

## Mini controller

> [!WARNING]
> **Warning:** In general, Google Play Services must be installed and up-to-date or Cast SDK won't work.

Instead of CCL's `MiniController`, use CAF's `MiniControllerFragment` in your
app layout file of the activities in which you want to show the mini
controller:

    <fragment
            android:id="@+id/cast_mini_controller"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            app:castShowImageThumbnail="true"
            android:visibility="gone"
            class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

CAF does not support the manual configuration supported by CCL's `MiniController`
and also does not support the `Autoplay` feature.

To customize the styling and buttons of the mini controller, follow the
procedure
[Customize Mini Controller](https://developers.google.com/cast/docs/android_sender_advanced#customize_mini_controller).

## Notification and lock screen

Similar to CCL's `VideoCastNotificationService`, CAF provides a
`MediaNotificationService` to manage the display of media notifications
when casting.

You need to remove the following from your manifest:

- `VideoIntentReceiver`
- `VideoCastNotificationService`

CCL supports providing a custom notification service with the
`CastConfiguration.Builder`; that is not supported by CAF.

Consider the following `CastManager` initialization using CCL:

    VideoCastManager.initialize(
       getApplicationContext(),
       new CastConfiguration.Builder(
               context.getString(R.string.app_id))
           .addNotificationAction(
               CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE,true)
           .addNotificationAction(
               CastConfiguration.NOTIFICATION_ACTION_DISCONNECT,true)
           .build());

For the equivalent configuration in CAF, the SDK provides a
`NotificationsOptions.Builder` to help you build media controls for the
notification and lock screen into the sender app. The notification and lock
screen controls can be enabled with the `CastOptions` when initializing the
`CastContext`.

    public CastOptions getCastOptions(Context context) {
        NotificationOptions notificationOptions = 
            new NotificationOptions.Builder()
                .setActions(Arrays.asList(
                    MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK,
                    MediaIntentReceiver.ACTION_STOP_CASTING), new int[]{0, 1})
                .build();
        CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
                 .setNotificationOptions(notificationOptions)
                 .build();
        return new CastOptions.Builder()
                 .setReceiverApplicationId(context.getString(R.string.app_id))
                 .setCastMediaOptions(mediaOptions)
                 .build();
    }

Notifications and lock screen controls are always enabled in CAF. Also, note that
the play/pause and stop casting buttons are provided by default. CAF
will automatically track the visibility of Activities for deciding
when to display the media notification, except for Gingerbread.
(For Gingerbread, see the [earlier note](https://developers.google.com/cast/docs/migrate_v2/ccl_sender#gingerbread) on
using `registerLifecycleCallbacksBeforeIceCreamSandwich()`; CCL's
`VideoCastManager` `incrementUiCounter` and `decrementUiCounter` calls
should be removed.)

To customize the buttons that are displayed in notifications, follow the
procedure
[Add Media Controls to Notification and Lock Screen](https://developers.google.com/cast/docs/android_sender_integrate#add_media_controls_to_notification_and_lock_screen).

## Expanded controller

CCL provides the `VideoCastControllerActivity` and `VideoCastControllerFragment`
to display an expanded controller when casting media.

You can remove the `VideoCastControllerActivity` declaration in the manifest.

In CAF, you have to
[extend the ExpandedControllerActivity and add the Cast button](https://developers.google.com/cast/docs/android_sender_integrate#add_expanded_controller).

To customize the styles and buttons that are displayed in the expanded
controller, follow the procedure
[Customize Expanded Controller](https://developers.google.com/cast/docs/android_sender_advanced#customize_expanded_controller).

## Audio focus

As with CCL, audio focus is managed automatically.

## Volume control

For Gingerbread, `dispatchKeyEvent` is required as with CCL. In ICS and above
for both CCL and CAF volume control is handled automatically.

CAF enables controlling the cast volume through the hard volume button on the
phone inside your apps activities and also shows a visual volume bar when
casting on supported versions. CAF also handles change of volume through the
hard volume even if your app is not in front, is locked or even if the screen is
off.

## Closed captions

In Android KitKat and above, captions can be customized through Captions
Settings, found under Settings ­\> Accessibility. Earlier versions of Android,
however, do not have this capability. CCL handles this by providing custom
settings for earlier versions and delegating to the system settings on KitKat
and above.

CAF does not provide custom settings to change the caption preferences. You
should remove the `CaptionsPreferenceActivity` references in your manifest
and your preferences XML.

CCL's `TracksChooserDialog` is no longer needed since changing the closed
captions tracks is handled by the expanded controller UI.

The [closed captioning API](https://developers.google.com/cast/docs/android_sender_advanced#use_media_tracks)
in CAF is similar to v2.

## Debug logging

CAF does not provide debug logging settings.

## Misc

The following CCL features are not supported in CAF:

- Obtaining authorization prior to playback by providing a `MediaAuthService`
- Configurable UI messages

## Sample apps

Take a look at the [diff](https://github.com/googlesamples/android-UniversalMusicPlayer/commit/39fa286313639b5ce069e755c18762aaa1f59ea9) for migrating our [Universal Music Player for Android](https://github.com/googlesamples/android-UniversalMusicPlayer) (uamp) sample app from CCL to CAF.

We also have [codelab tutorials](https://codelabs.developers.google.com/codelabs/cast-videos-android/) and [sample apps](https://github.com/googlecast/CastVideos-android) that use CAF.