1/* 2 * Copyright (C) 2017 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 17package android.support.media.tv; 18 19import android.content.ContentResolver; 20import android.content.Context; 21import android.database.sqlite.SQLiteException; 22import android.graphics.Bitmap; 23import android.graphics.BitmapFactory; 24import android.media.tv.TvContract; 25import android.net.Uri; 26import android.support.annotation.NonNull; 27import android.support.annotation.WorkerThread; 28import android.util.Log; 29 30import java.io.FileNotFoundException; 31import java.io.IOException; 32import java.io.InputStream; 33import java.io.OutputStream; 34import java.net.HttpURLConnection; 35import java.net.URL; 36import java.net.URLConnection; 37 38/** A utility class for conveniently storing and loading channel logos. */ 39@WorkerThread 40public class ChannelLogoUtils { 41 private static final String TAG = "ChannelLogoUtils"; 42 43 private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec 44 private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec 45 46 /** 47 * Stores channel logo in the system content provider from the given URI. The method will try 48 * to fetch the image file and decode it into {@link Bitmap}. Once the image is successfully 49 * fetched, it will be stored in the system content provider and associated with the given 50 * channel ID. 51 * 52 * <p>The URI provided to this method can be a URL referring to a image file residing in some 53 * remote site/server, or a URI in one of the following formats: 54 * 55 * <ul> 56 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 57 * <li>android.resource ({@link android.content.ContentResolver 58 * #SCHEME_ANDROID_RESOURCE})</li> 59 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 60 * </ul> 61 * 62 * <p>This method should be run in a worker thread since it may require network connection, 63 * which will raise an exception if it's running in the main thread. 64 * 65 * @param context the context used to access the system content provider 66 * @param channelId the ID of the target channel with which the fetched logo should be 67 * associated 68 * @param logoUri the {@link Uri} of the logo file to be fetched and stored in the system 69 * provider 70 * 71 * @return {@code true} if successfully fetched the image file referred by the give logo URI 72 * and stored it in the system content provider, or {@code false} if failed. 73 * 74 * @see #loadChannelLogo(Context, long) 75 */ 76 public static boolean storeChannelLogo(@NonNull Context context, long channelId, 77 @NonNull Uri logoUri) { 78 String scheme = logoUri.normalizeScheme().getScheme(); 79 URLConnection urlConnection = null; 80 InputStream inputStream = null; 81 Bitmap fetchedLogo = null; 82 try { 83 if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme) 84 || ContentResolver.SCHEME_FILE.equals(scheme) 85 || ContentResolver.SCHEME_CONTENT.equals(scheme)) { 86 // A local resource 87 inputStream = context.getContentResolver().openInputStream(logoUri); 88 } else { 89 // A remote resource, should be an valid URL. 90 urlConnection = getUrlConnection(logoUri.toString()); 91 inputStream = urlConnection.getInputStream(); 92 } 93 fetchedLogo = BitmapFactory.decodeStream(inputStream); 94 } catch (IOException e) { 95 Log.i(TAG, "Failed to get logo from the URI: " + logoUri + "\n", e); 96 } finally { 97 if (inputStream != null) { 98 try { 99 inputStream.close(); 100 } catch (IOException e) { 101 // Do nothing. 102 } 103 } 104 if (urlConnection instanceof HttpURLConnection) { 105 ((HttpURLConnection) urlConnection).disconnect(); 106 } 107 } 108 return fetchedLogo != null && storeChannelLogo(context, channelId, fetchedLogo); 109 } 110 111 /** 112 * Stores the given channel logo {@link Bitmap} in the system content provider and associate 113 * it with the given channel ID. 114 * 115 * @param context the context used to access the system content provider 116 * @param channelId the ID of the target channel with which the given logo should be associated 117 * @param logo the logo image to be stored 118 * 119 * @return {@code true} if successfully stored the logo in the system content provider, 120 * otherwise {@code false}. 121 * 122 * @see #loadChannelLogo(Context, long) 123 */ 124 public static boolean storeChannelLogo(@NonNull Context context, long channelId, 125 @NonNull Bitmap logo) { 126 boolean result = false; 127 Uri localUri = TvContract.buildChannelLogoUri(channelId); 128 try (OutputStream outputStream = context.getContentResolver().openOutputStream(localUri)) { 129 result = logo.compress(Bitmap.CompressFormat.PNG, 100, outputStream); 130 outputStream.flush(); 131 } catch (SQLiteException | IOException e) { 132 Log.i(TAG, "Failed to store the logo to the system content provider.\n", e); 133 } 134 return result; 135 } 136 137 /** 138 * A convenient helper method to get the channel logo associated to the given channel ID from 139 * the system content provider. 140 * 141 * @param context the context used to access the system content provider 142 * @param channelId the ID of the channel whose logo is supposed to be loaded 143 * 144 * @return the requested channel logo in {@link Bitmap}, or {@code null} if not available. 145 * 146 * @see #storeChannelLogo(Context, long, Uri) 147 * @see #storeChannelLogo(Context, long, Bitmap) 148 */ 149 public static Bitmap loadChannelLogo(@NonNull Context context, long channelId) { 150 Bitmap channelLogo = null; 151 try { 152 channelLogo = BitmapFactory.decodeStream(context.getContentResolver().openInputStream( 153 TvContract.buildChannelLogoUri(channelId))); 154 } catch (FileNotFoundException e) { 155 // Channel logo is not found in the content provider. 156 Log.i(TAG, "Channel logo for channel (ID:" + channelId + ") not found.", e); 157 } 158 return channelLogo; 159 } 160 161 private static URLConnection getUrlConnection(String uriString) throws IOException { 162 URLConnection urlConnection = new URL(uriString).openConnection(); 163 urlConnection.setConnectTimeout(CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION); 164 urlConnection.setReadTimeout(READ_TIMEOUT_MS_FOR_URLCONNECTION); 165 return urlConnection; 166 } 167} 168