1 package com.example.android.tv.channelsprograms.util;
2 
3 import android.net.Uri;
4 import android.support.annotation.StringDef;
5 
6 import java.util.List;
7 
8 /** Builds and parses uris for deep linking within the app. */
9 public class AppLinkHelper {
10 
11     private static final String SCHEMA_URI_PREFIX = "tvrecommendation://app/";
12     public static final String PLAYBACK = "playback";
13     public static final String BROWSE = "browse";
14     private static final String URI_PLAY = SCHEMA_URI_PREFIX + PLAYBACK;
15     private static final String URI_VIEW = SCHEMA_URI_PREFIX + BROWSE;
16     private static final int URI_INDEX_OPTION = 0;
17     private static final int URI_INDEX_CHANNEL = 1;
18     private static final int URI_INDEX_MOVIE = 2;
19     private static final int URI_INDEX_POSITION = 3;
20     public static final int DEFAULT_POSITION = -1;
21 
22     /**
23      * Builds a {@link Uri} for deep link into playing a movie from the beginning.
24      *
25      * @param channelId - id of the channel the movie is in.
26      * @param movieId - id of the movie.
27      * @return a uri.
28      */
buildPlaybackUri(long channelId, long movieId)29     public static Uri buildPlaybackUri(long channelId, long movieId) {
30         return buildPlaybackUri(channelId, movieId, DEFAULT_POSITION);
31     }
32 
33     /**
34      * Builds a {@link Uri} to deep link into continue playing a movie from a position.
35      *
36      * @param channelId - id of the channel the movie is in.
37      * @param movieId - id of the movie.
38      * @param position - position to continue playing.
39      * @return a uri.
40      */
buildPlaybackUri(long channelId, long movieId, long position)41     public static Uri buildPlaybackUri(long channelId, long movieId, long position) {
42         return Uri.parse(URI_PLAY)
43                 .buildUpon()
44                 .appendPath(String.valueOf(channelId))
45                 .appendPath(String.valueOf(movieId))
46                 .appendPath(String.valueOf(position))
47                 .build();
48     }
49 
50     /**
51      * Builds a {@link Uri} to deep link into viewing a subscription.
52      *
53      * @param subscriptionName - name of the subscription.
54      * @return a uri.
55      */
buildBrowseUri(String subscriptionName)56     public static Uri buildBrowseUri(String subscriptionName) {
57         return Uri.parse(URI_VIEW).buildUpon().appendPath(subscriptionName).build();
58     }
59 
60     /**
61      * Returns an {@link AppLinkAction} for the given Uri.
62      *
63      * @param uri to determine the intended action.
64      * @return an action.
65      */
extractAction(Uri uri)66     public static AppLinkAction extractAction(Uri uri) {
67         if (isPlaybackUri(uri)) {
68             return new PlaybackAction(
69                     extractChannelId(uri), extractMovieId(uri), extractPosition(uri));
70         } else if (isBrowseUri(uri)) {
71             return new BrowseAction(extractSubscriptionName(uri));
72         }
73         throw new IllegalArgumentException("No action found for uri " + uri);
74     }
75 
76     /**
77      * Tests if the {@link Uri} was built for playing a movie.
78      *
79      * @param uri to examine.
80      * @return true if the uri is for playing a movie.
81      */
isPlaybackUri(Uri uri)82     private static boolean isPlaybackUri(Uri uri) {
83         if (uri.getPathSegments().isEmpty()) {
84             return false;
85         }
86         String option = uri.getPathSegments().get(URI_INDEX_OPTION);
87         return PLAYBACK.equals(option);
88     }
89 
90     /**
91      * Tests if a {@link Uri} was built for browsing a subscription.
92      *
93      * @param uri to examine.
94      * @return true if the Uri is for browsing a subscription.
95      */
isBrowseUri(Uri uri)96     private static boolean isBrowseUri(Uri uri) {
97         if (uri.getPathSegments().isEmpty()) {
98             return false;
99         }
100         String option = uri.getPathSegments().get(URI_INDEX_OPTION);
101         return BROWSE.equals(option);
102     }
103 
104     /**
105      * Extracts the subscription name from the {@link Uri}.
106      *
107      * @param uri that contains a subscription name.
108      * @return the subscription name.
109      */
extractSubscriptionName(Uri uri)110     private static String extractSubscriptionName(Uri uri) {
111         return extract(uri, URI_INDEX_CHANNEL);
112     }
113 
114     /**
115      * Extracts the channel id from the {@link Uri}.
116      *
117      * @param uri that contains a channel id.
118      * @return the channel id.
119      */
extractChannelId(Uri uri)120     private static long extractChannelId(Uri uri) {
121         return extractLong(uri, URI_INDEX_CHANNEL);
122     }
123 
124     /**
125      * Extracts the movie id from the {@link Uri}.
126      *
127      * @param uri that contains a movie id.
128      * @return the movie id.
129      */
extractMovieId(Uri uri)130     private static long extractMovieId(Uri uri) {
131         return extractLong(uri, URI_INDEX_MOVIE);
132     }
133 
134     /**
135      * Extracts the playback mPosition from the {@link Uri}.
136      *
137      * @param uri that contains a playback mPosition.
138      * @return the playback mPosition.
139      */
extractPosition(Uri uri)140     private static long extractPosition(Uri uri) {
141         return extractLong(uri, URI_INDEX_POSITION);
142     }
143 
extractLong(Uri uri, int index)144     private static long extractLong(Uri uri, int index) {
145         return Long.valueOf(extract(uri, index));
146     }
147 
extract(Uri uri, int index)148     private static String extract(Uri uri, int index) {
149         List<String> pathSegments = uri.getPathSegments();
150         if (pathSegments.isEmpty() || pathSegments.size() < index) {
151             return null;
152         }
153         return pathSegments.get(index);
154     }
155 
156     @StringDef({BROWSE, PLAYBACK})
157     public @interface ActionFlags {}
158 
159     /** Action for deep linking. */
160     public interface AppLinkAction {
161         /** Returns an string representation of the action. */
162         @ActionFlags
getAction()163         String getAction();
164     }
165 
166     /** Browse a subscription. */
167     public static class BrowseAction implements AppLinkAction {
168 
169         private final String mSubscriptionName;
170 
BrowseAction(String subscriptionName)171         private BrowseAction(String subscriptionName) {
172             this.mSubscriptionName = subscriptionName;
173         }
174 
getSubscriptionName()175         public String getSubscriptionName() {
176             return mSubscriptionName;
177         }
178 
179         @Override
getAction()180         public String getAction() {
181             return BROWSE;
182         }
183     }
184 
185     /** Play a movie. */
186     public static class PlaybackAction implements AppLinkAction {
187 
188         private final long mChannelId;
189         private final long mMovieId;
190         private final long mPosition;
191 
PlaybackAction(long channelId, long movieId, long position)192         private PlaybackAction(long channelId, long movieId, long position) {
193             this.mChannelId = channelId;
194             this.mMovieId = movieId;
195             this.mPosition = position;
196         }
197 
getChannelId()198         public long getChannelId() {
199             return mChannelId;
200         }
201 
getMovieId()202         public long getMovieId() {
203             return mMovieId;
204         }
205 
getPosition()206         public long getPosition() {
207             return mPosition;
208         }
209 
210         @Override
getAction()211         public String getAction() {
212             return PLAYBACK;
213         }
214     }
215 }
216