1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tv.testing; 17 18 import android.content.ContentResolver; 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.media.tv.TvContract; 23 import android.media.tv.TvContract.Channels; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.support.annotation.WorkerThread; 27 import android.text.TextUtils; 28 import android.util.Log; 29 import android.util.SparseArray; 30 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 38 /** 39 * Static helper methods for working with {@link android.media.tv.TvContract}. 40 */ 41 public class ChannelUtils { 42 private static final String TAG = "ChannelUtils"; 43 private static final boolean DEBUG = false; 44 45 /** 46 * Query and return the map of (channel_id, ChannelInfo). 47 * See: {@link ChannelInfo#fromCursor(Cursor)}. 48 */ 49 @WorkerThread queryChannelInfoMapForTvInput( Context context, String inputId)50 public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput( 51 Context context, String inputId) { 52 Uri uri = TvContract.buildChannelsUriForInput(inputId); 53 Map<Long, ChannelInfo> map = new HashMap<>(); 54 55 String[] projections = new String[ChannelInfo.PROJECTION.length + 1]; 56 projections[0] = Channels._ID; 57 System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length); 58 try (Cursor cursor = context.getContentResolver() 59 .query(uri, projections, null, null, null)) { 60 if (cursor != null) { 61 while (cursor.moveToNext()) { 62 map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor)); 63 } 64 } 65 return map; 66 } 67 } 68 69 @WorkerThread updateChannels(Context context, String inputId, List<ChannelInfo> channels)70 public static void updateChannels(Context context, String inputId, List<ChannelInfo> channels) { 71 // Create a map from original network ID to channel row ID for existing channels. 72 SparseArray<Long> existingChannelsMap = new SparseArray<>(); 73 Uri channelsUri = TvContract.buildChannelsUriForInput(inputId); 74 String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID}; 75 ContentResolver resolver = context.getContentResolver(); 76 try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) { 77 while (cursor != null && cursor.moveToNext()) { 78 long rowId = cursor.getLong(0); 79 int originalNetworkId = cursor.getInt(1); 80 existingChannelsMap.put(originalNetworkId, rowId); 81 } 82 } 83 84 Map<Uri, String> logos = new HashMap<>(); 85 for (ChannelInfo channel : channels) { 86 // If a channel exists, update it. If not, insert a new one. 87 ContentValues values = new ContentValues(); 88 values.put(Channels.COLUMN_INPUT_ID, inputId); 89 values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number); 90 values.put(Channels.COLUMN_DISPLAY_NAME, channel.name); 91 values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId); 92 String videoFormat = channel.getVideoFormat(); 93 if (videoFormat != null) { 94 values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat); 95 } else { 96 values.putNull(Channels.COLUMN_VIDEO_FORMAT); 97 } 98 if (!TextUtils.isEmpty(channel.appLinkText)) { 99 values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText); 100 } 101 if (channel.appLinkColor != 0) { 102 values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor); 103 } 104 if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) { 105 values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri); 106 } 107 if (!TextUtils.isEmpty(channel.appLinkIconUri)) { 108 values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri); 109 } 110 if (!TextUtils.isEmpty(channel.appLinkIntentUri)) { 111 values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri); 112 } 113 Long rowId = existingChannelsMap.get(channel.originalNetworkId); 114 Uri uri; 115 if (rowId == null) { 116 if (DEBUG) Log.d(TAG, "Inserting "+ channel); 117 uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); 118 } else { 119 if (DEBUG) Log.d(TAG, "Updating "+ channel); 120 uri = TvContract.buildChannelUri(rowId); 121 resolver.update(uri, values, null, null); 122 existingChannelsMap.remove(channel.originalNetworkId); 123 } 124 if (!TextUtils.isEmpty(channel.logoUrl)) { 125 logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl); 126 } 127 } 128 if (!logos.isEmpty()) { 129 new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos); 130 } 131 132 // Deletes channels which don't exist in the new feed. 133 int size = existingChannelsMap.size(); 134 for (int i = 0; i < size; ++i) { 135 Long rowId = existingChannelsMap.valueAt(i); 136 resolver.delete(TvContract.buildChannelUri(rowId), null, null); 137 } 138 } 139 copy(InputStream is, OutputStream os)140 public static void copy(InputStream is, OutputStream os) throws IOException { 141 byte[] buffer = new byte[1024]; 142 int len; 143 while ((len = is.read(buffer)) != -1) { 144 os.write(buffer, 0, len); 145 } 146 } 147 ChannelUtils()148 private ChannelUtils() { 149 // Prevent instantiation. 150 } 151 152 public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> { 153 private final Context mContext; 154 InsertLogosTask(Context context)155 InsertLogosTask(Context context) { 156 mContext = context; 157 } 158 159 @SafeVarargs 160 @Override doInBackground(Map<Uri, String>.... logosList)161 public final Void doInBackground(Map<Uri, String>... logosList) { 162 for (Map<Uri, String> logos : logosList) { 163 for (Uri uri : logos.keySet()) { 164 if (uri == null) { 165 continue; 166 } 167 Uri logoUri = Uri.parse(logos.get(uri)); 168 try (InputStream is = mContext.getContentResolver().openInputStream(logoUri); 169 OutputStream os = mContext.getContentResolver().openOutputStream(uri)) { 170 copy(is, os); 171 } catch (IOException ioe) { 172 Log.e(TAG, "Failed to write " + logoUri + " to " + uri, ioe); 173 } 174 } 175 } 176 return null; 177 } 178 } 179 } 180