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