1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/*
2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project
3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License.
6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at
7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software
11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and
14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License.
15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv.testing;
17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver;
19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentValues;
20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context;
21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.database.Cursor;
22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract;
23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract.Channels;
24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.net.Uri;
25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.AsyncTask;
26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Build;
27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.WorkerThread;
28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.text.TextUtils;
29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log;
30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.SparseArray;
31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.io.IOException;
33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.io.InputStream;
34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.io.OutputStream;
35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashMap;
36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List;
37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Map;
38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/**
40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Static helper methods for working with {@link android.media.tv.TvContract}.
41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class ChannelUtils {
43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String TAG = "ChannelUtils";
441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final boolean DEBUG = false;
45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Query and return the map of (channel_id, ChannelInfo).
48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * See: {@link ChannelInfo#fromCursor(Cursor)}.
49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @WorkerThread
51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput(
52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Context context, String inputId) {
53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Uri uri = TvContract.buildChannelsUriForInput(inputId);
54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Map<Long, ChannelInfo> map = new HashMap<>();
55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String[] projections = new String[ChannelInfo.PROJECTION.length + 1];
57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        projections[0] = Channels._ID;
5807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length);
59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        try (Cursor cursor = context.getContentResolver()
60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                .query(uri, projections, null, null, null)) {
61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (cursor != null) {
62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (cursor.moveToNext()) {
63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor));
64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return map;
67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @WorkerThread
71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static void updateChannels(Context context, String inputId, List<ChannelInfo> channels) {
72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Create a map from original network ID to channel row ID for existing channels.
73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        SparseArray<Long> existingChannelsMap = new SparseArray<>();
74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Uri channelsUri = TvContract.buildChannelsUriForInput(inputId);
75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID};
76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ContentResolver resolver = context.getContentResolver();
77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) {
78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            while (cursor != null && cursor.moveToNext()) {
79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long rowId = cursor.getLong(0);
80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                int originalNetworkId = cursor.getInt(1);
81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                existingChannelsMap.put(originalNetworkId, rowId);
82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Map<Uri, String> logos = new HashMap<>();
86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (ChannelInfo channel : channels) {
87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // If a channel exists, update it. If not, insert a new one.
88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            ContentValues values = new ContentValues();
89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            values.put(Channels.COLUMN_INPUT_ID, inputId);
90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            String videoFormat = channel.getVideoFormat();
94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (videoFormat != null) {
95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat);
96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                values.putNull(Channels.COLUMN_VIDEO_FORMAT);
98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
99ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!TextUtils.isEmpty(channel.appLinkText)) {
101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText);
102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (channel.appLinkColor != 0) {
104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor);
105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) {
107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri);
108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!TextUtils.isEmpty(channel.appLinkIconUri)) {
110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri);
111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!TextUtils.isEmpty(channel.appLinkIntentUri)) {
113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri);
114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Long rowId = existingChannelsMap.get(channel.originalNetworkId);
117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Uri uri;
118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (rowId == null) {
1191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) Log.d(TAG, "Inserting "+ channel);
120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) Log.d(TAG, "Updating "+ channel);
123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                uri = TvContract.buildChannelUri(rowId);
124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                resolver.update(uri, values, null, null);
125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                existingChannelsMap.remove(channel.originalNetworkId);
126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (!TextUtils.isEmpty(channel.logoUrl)) {
128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl);
129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!logos.isEmpty()) {
132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos);
133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Deletes channels which don't exist in the new feed.
136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int size = existingChannelsMap.size();
137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (int i = 0; i < size; ++i) {
138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Long rowId = existingChannelsMap.valueAt(i);
139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            resolver.delete(TvContract.buildChannelUri(rowId), null, null);
140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static void copy(InputStream is, OutputStream os) throws IOException {
144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        byte[] buffer = new byte[1024];
145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int len;
146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        while ((len = is.read(buffer)) != -1) {
147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            os.write(buffer, 0, len);
148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private ChannelUtils() {
152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Prevent instantiation.
153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> {
156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final Context mContext;
157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        InsertLogosTask(Context context) {
159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mContext = context;
160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @SafeVarargs
163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public final Void doInBackground(Map<Uri, String>... logosList) {
165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (Map<Uri, String> logos : logosList) {
166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                for (Uri uri : logos.keySet()) {
167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (uri == null) {
168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        continue;
169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Uri logoUri = Uri.parse(logos.get(uri));
171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    try (InputStream is = mContext.getContentResolver().openInputStream(logoUri);
172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            OutputStream os = mContext.getContentResolver().openOutputStream(uri)) {
173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        copy(is, os);
174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    } catch (IOException ioe) {
175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Log.e(TAG, "Failed to write " + logoUri + "  to " + uri, ioe);
176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return null;
180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko}
183