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