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.data; 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 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 38 /** Static helper methods for working with {@link android.media.tv.TvContract}. */ 39 public class ChannelUtils { 40 private static final String TAG = "ChannelUtils"; 41 private static final boolean DEBUG = false; 42 43 /** 44 * Query and return the map of (channel_id, ChannelInfo). See: {@link 45 * com.android.tv.testing.data.ChannelInfo#fromCursor(Cursor)}. 46 */ 47 @WorkerThread queryChannelInfoMapForTvInput( Context context, String inputId)48 public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput( 49 Context context, String inputId) { 50 Uri uri = TvContract.buildChannelsUriForInput(inputId); 51 Map<Long, ChannelInfo> map = new HashMap<>(); 52 53 String[] projections = new String[ChannelInfo.PROJECTION.length + 1]; 54 projections[0] = Channels._ID; 55 System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length); 56 try (Cursor cursor = 57 context.getContentResolver().query(uri, projections, null, null, null)) { 58 if (cursor != null) { 59 while (cursor.moveToNext()) { 60 map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor)); 61 } 62 } 63 return map; 64 } 65 } 66 67 @WorkerThread updateChannels(Context context, String inputId, List<ChannelInfo> channels)68 public static void updateChannels(Context context, String inputId, List<ChannelInfo> channels) { 69 // Create a map from original network ID to channel row ID for existing channels. 70 SparseArray<Long> existingChannelsMap = new SparseArray<>(); 71 Uri channelsUri = TvContract.buildChannelsUriForInput(inputId); 72 String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID}; 73 ContentResolver resolver = context.getContentResolver(); 74 try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) { 75 while (cursor != null && cursor.moveToNext()) { 76 long rowId = cursor.getLong(0); 77 int originalNetworkId = cursor.getInt(1); 78 existingChannelsMap.put(originalNetworkId, rowId); 79 } 80 } 81 82 Map<Uri, String> logos = new HashMap<>(); 83 for (ChannelInfo channel : channels) { 84 // If a channel exists, update it. If not, insert a new one. 85 ContentValues values = new ContentValues(); 86 values.put(Channels.COLUMN_INPUT_ID, inputId); 87 values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number); 88 values.put(Channels.COLUMN_DISPLAY_NAME, channel.name); 89 values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId); 90 String videoFormat = channel.getVideoFormat(); 91 if (videoFormat != null) { 92 values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat); 93 } else { 94 values.putNull(Channels.COLUMN_VIDEO_FORMAT); 95 } 96 if (!TextUtils.isEmpty(channel.appLinkText)) { 97 values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText); 98 } 99 if (channel.appLinkColor != 0) { 100 values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor); 101 } 102 if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) { 103 values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri); 104 } 105 if (!TextUtils.isEmpty(channel.appLinkIconUri)) { 106 values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri); 107 } 108 if (!TextUtils.isEmpty(channel.appLinkIntentUri)) { 109 values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri); 110 } 111 Long rowId = existingChannelsMap.get(channel.originalNetworkId); 112 Uri uri; 113 if (rowId == null) { 114 if (DEBUG) { 115 Log.d(TAG, "Inserting " + channel); 116 } 117 uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); 118 } else { 119 if (DEBUG) { 120 Log.d(TAG, "Updating " + channel); 121 } 122 uri = TvContract.buildChannelUri(rowId); 123 resolver.update(uri, values, null, null); 124 existingChannelsMap.remove(channel.originalNetworkId); 125 } 126 if (!TextUtils.isEmpty(channel.logoUrl)) { 127 logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl); 128 } 129 } 130 if (!logos.isEmpty()) { 131 new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos); 132 } 133 134 // Deletes channels which don't exist in the new feed. 135 int size = existingChannelsMap.size(); 136 for (int i = 0; i < size; ++i) { 137 Long rowId = existingChannelsMap.valueAt(i); 138 resolver.delete(TvContract.buildChannelUri(rowId), null, null); 139 } 140 } 141 copy(InputStream is, OutputStream os)142 public static void copy(InputStream is, OutputStream os) throws IOException { 143 byte[] buffer = new byte[1024]; 144 int len; 145 while ((len = is.read(buffer)) != -1) { 146 os.write(buffer, 0, len); 147 } 148 } 149 ChannelUtils()150 private ChannelUtils() { 151 // Prevent instantiation. 152 } 153 createChannelInfos(Context context, int channelCount)154 public static List<ChannelInfo> createChannelInfos(Context context, int channelCount) { 155 List<ChannelInfo> channels = new ArrayList<>(); 156 for (int i = 1; i <= channelCount; i++) { 157 channels.add(ChannelInfo.create(context, i)); 158 } 159 return channels; 160 } 161 162 public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> { 163 private final Context mContext; 164 InsertLogosTask(Context context)165 InsertLogosTask(Context context) { 166 mContext = context; 167 } 168 169 @SafeVarargs 170 @Override doInBackground(Map<Uri, String>.... logosList)171 public final Void doInBackground(Map<Uri, String>... logosList) { 172 for (Map<Uri, String> logos : logosList) { 173 for (Uri uri : logos.keySet()) { 174 if (uri == null) { 175 continue; 176 } 177 Uri logoUri = Uri.parse(logos.get(uri)); 178 try (InputStream is = mContext.getContentResolver().openInputStream(logoUri); 179 OutputStream os = mContext.getContentResolver().openOutputStream(uri)) { 180 copy(is, os); 181 } catch (IOException ioe) { 182 Log.e(TAG, "Failed to write " + logoUri + " to " + uri, ioe); 183 } 184 } 185 } 186 return null; 187 } 188 } 189 } 190