/* * Copyright (C) 2015 The Android Open Source Project * * 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.android.tv.testing; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Static helper methods for working with {@link android.media.tv.TvContract}. */ public class ChannelUtils { private static final String TAG = "ChannelUtils"; private static final boolean DEBUG = false; /** * Query and return the map of (channel_id, ChannelInfo). * See: {@link ChannelInfo#fromCursor(Cursor)}. */ @WorkerThread public static Map queryChannelInfoMapForTvInput( Context context, String inputId) { Uri uri = TvContract.buildChannelsUriForInput(inputId); Map map = new HashMap<>(); String[] projections = new String[ChannelInfo.PROJECTION.length + 1]; projections[0] = Channels._ID; System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length); try (Cursor cursor = context.getContentResolver() .query(uri, projections, null, null, null)) { if (cursor != null) { while (cursor.moveToNext()) { map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor)); } } return map; } } @WorkerThread public static void updateChannels(Context context, String inputId, List channels) { // Create a map from original network ID to channel row ID for existing channels. SparseArray existingChannelsMap = new SparseArray<>(); Uri channelsUri = TvContract.buildChannelsUriForInput(inputId); String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID}; ContentResolver resolver = context.getContentResolver(); try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) { while (cursor != null && cursor.moveToNext()) { long rowId = cursor.getLong(0); int originalNetworkId = cursor.getInt(1); existingChannelsMap.put(originalNetworkId, rowId); } } Map logos = new HashMap<>(); for (ChannelInfo channel : channels) { // If a channel exists, update it. If not, insert a new one. ContentValues values = new ContentValues(); values.put(Channels.COLUMN_INPUT_ID, inputId); values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number); values.put(Channels.COLUMN_DISPLAY_NAME, channel.name); values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId); String videoFormat = channel.getVideoFormat(); if (videoFormat != null) { values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat); } else { values.putNull(Channels.COLUMN_VIDEO_FORMAT); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!TextUtils.isEmpty(channel.appLinkText)) { values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText); } if (channel.appLinkColor != 0) { values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor); } if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) { values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri); } if (!TextUtils.isEmpty(channel.appLinkIconUri)) { values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri); } if (!TextUtils.isEmpty(channel.appLinkIntentUri)) { values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri); } } Long rowId = existingChannelsMap.get(channel.originalNetworkId); Uri uri; if (rowId == null) { if (DEBUG) Log.d(TAG, "Inserting "+ channel); uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); } else { if (DEBUG) Log.d(TAG, "Updating "+ channel); uri = TvContract.buildChannelUri(rowId); resolver.update(uri, values, null, null); existingChannelsMap.remove(channel.originalNetworkId); } if (!TextUtils.isEmpty(channel.logoUrl)) { logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl); } } if (!logos.isEmpty()) { new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos); } // Deletes channels which don't exist in the new feed. int size = existingChannelsMap.size(); for (int i = 0; i < size; ++i) { Long rowId = existingChannelsMap.valueAt(i); resolver.delete(TvContract.buildChannelUri(rowId), null, null); } } public static void copy(InputStream is, OutputStream os) throws IOException { byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } } private ChannelUtils() { // Prevent instantiation. } public static class InsertLogosTask extends AsyncTask, Void, Void> { private final Context mContext; InsertLogosTask(Context context) { mContext = context; } @SafeVarargs @Override public final Void doInBackground(Map... logosList) { for (Map logos : logosList) { for (Uri uri : logos.keySet()) { if (uri == null) { continue; } Uri logoUri = Uri.parse(logos.get(uri)); try (InputStream is = mContext.getContentResolver().openInputStream(logoUri); OutputStream os = mContext.getContentResolver().openOutputStream(uri)) { copy(is, os); } catch (IOException ioe) { Log.e(TAG, "Failed to write " + logoUri + " to " + uri, ioe); } } } return null; } } }