最近在做Android TV O的项目,需要在TV 桌面添加自定义频道/节目,节目的背景图片要显示为SD卡或者缓存目录里面的图片。

  1. 添加自定义频道
  2. 节目背景显示本地目录的图片

一、添加频道

1. 首先新建频道、节目实体类,属性如下。

public class MediaChannel {    private final String mName;    private final String mDescription;    private final String mMediaUri;    private final String mBgImage;    private final String mTitle;    private final String mMediaChannelId;    private List mPrograms;    private boolean mChannelPublished;    private long mChannelId;    MediaChannel(String name, List programs, String mediaChannelId) {        mName = name;        mTitle = "playlist title";        mDescription = "playlist description";        mMediaUri = "dsf";        mBgImage = "asdf";        mPrograms = programs;        mMediaChannelId = mediaChannelId;    }    // 省略 set get toString  }public class MediaProgram implements Parcelable {    private final String mMediaProgramId;    private final String mContentId;    private final String mTitle;    private final String mDescription;    private final String mBgImageUrl;    private final String mCardImageUrl;    private final String mMediaUrl;    private final String mPreviewMediaUrl;    private final String mCategory;    private long mProgramId;    private int mViewCount;    MediaProgram(String title, String description, String bgImageUrl, String cardImageUrl,                 String category, String mediaProgramId, String contentId) {        mMediaProgramId = mediaProgramId;        mContentId = contentId;        mTitle = title;        mDescription = description;        mBgImageUrl = bgImageUrl;        mCardImageUrl = cardImageUrl;        mMediaUrl = "";        mPreviewMediaUrl = "";        mCategory = category;    }    // 省略 set get toString }

2. 初始化频道、节目信息

private void initChannel() {        Uri usbUri = getUSBCardImageFileUri();        Uri pvrUri = getPVRCardImageFileUri();        grantUriPermissionToApp("com.google.android.tvlauncher", usbUri);        grantUriPermissionToApp("com.google.android.tvlauncher", pvrUri);        String bgImageUrl = "";        String usbCardImageUrl = getUSBCardImageFileUri().toString();        String pvrCardImageUrl = getPVRCardImageFileUri().toString();        int mediaProgramId = 1;        int contentId = 0;        MediaProgram usbProgram = new MediaProgram("USB", "usb description", bgImageUrl, usbCardImageUrl,                "USB category", Integer.toString(mediaProgramId), Integer.toString(contentId ++));        MediaProgram pvrProgram = new MediaProgram("PVR", "pvr description", bgImageUrl, pvrCardImageUrl,                "PVR category", Integer.toString(mediaProgramId), Integer.toString(contentId ++));        List programs = new ArrayList<>();        programs.add(usbProgram);        programs.add(pvrProgram);        mChannelId = LocalDataManager.getChannelId(this);        mChannel = new MediaChannel("MediaChannel", programs, Long.toString(mChannelId));}    private Uri getUSBCardImageFileUri() {        String sdPath = Environment.getExternalStorageDirectory().getPath();        File file = new File(sdPath + "/Pictures/mediachannel/usb_thumbnail.jpg");        Uri uri = FileProvider.getUriForFile(this, "com.rogera.mediaplaychannel.fileprovider", file);        Log.v(TAG, "uri:" + uri.toString());        return uri;    }    private Uri getPVRCardImageFileUri() {        String sdPath = Environment.getExternalStorageDirectory().getPath();        File file = new File(sdPath + "/Pictures/mediachannel/pvr_thumbnail.jpg");        Uri uri = FileProvider.getUriForFile(this, "com.rogera.mediaplaychannel.fileprovider", file);        Log.v(TAG, "uri:" + uri.toString());        return uri;    }    private void grantUriPermissionToApp(String packageName, Uri uri) {        grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);    }

3.添加频道、节目

mChannelId = MediaTVProvider.addChannel(MainActivity.this, mChannel);

mChannel 为initChannel() 方法里面初始化的实体类

贴出核心类MediaTVProvider.java

package com.rogera.mediaplaychannel;import android.content.ComponentName;import android.content.ContentUris;import android.content.Context;import android.database.Cursor;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.net.Uri;import android.support.annotation.DrawableRes;import android.support.annotation.WorkerThread;import android.support.media.tv.Channel;import android.support.media.tv.ChannelLogoUtils;import android.support.media.tv.PreviewProgram;import android.support.media.tv.TvContractCompat;import android.text.TextUtils;import android.util.Log;import java.util.List;/** * Created by rogera on 2017/12/30. */public class MediaTVProvider {    private static final String TAG = "MediaTVProvider";    private static final String SCHEME = "tvmediachannels";    private static final String APPS_LAUNCH_HOST = "com.google.android.tvmediachannels";    private static final String PLAY_MEDIA_ACTION_PATH = "playMedia";    private static final String START_APP_ACTION_PATH = "startApp";    private static final Uri PREVIEW_PROGRAMS_CONTENT_URI =            Uri.parse("content://android.media.tv/preview_program");    static private String createInputId(Context context) {        ComponentName cName = new ComponentName(context, MainActivity.class.getName());        return TvContractCompat.buildInputId(cName);    }    @WorkerThread    static long addChannel(Context context, MediaChannel mediaChannel) {        String channelInputId = createInputId(context);        Channel channel = new Channel.Builder()                .setDisplayName(mediaChannel.getName())                .setDescription(mediaChannel.getDescription())                .setType(TvContractCompat.Channels.TYPE_PREVIEW)                .setInputId(channelInputId)                .setAppLinkIntentUri(Uri.parse(SCHEME + "://" + APPS_LAUNCH_HOST                        + "/" + START_APP_ACTION_PATH))                .setInternalProviderId(mediaChannel.getMediaChannelId())                .build();        Uri channelUri = context.getContentResolver().insert(TvContractCompat.Channels.CONTENT_URI,                channel.toContentValues());        if (channelUri == null || channelUri.equals(Uri.EMPTY)) {            Log.e(TAG, "addChannel Insert channel failed");            return 0;        }        long channelId = ContentUris.parseId(channelUri);        mediaChannel.setChannelPublishedId(channelId);        writeChannelLogo(context, channelId, R.drawable.media_logo);        List programs = mediaChannel.getMediaPrograms();        int weight = programs.size();        for (int i = 0; i < programs.size(); ++i, --weight) {            MediaProgram mp = programs.get(i);            final String mediaProgramId = mp.getMediaProgramId();            final String contentId = mp.getContentId();            PreviewProgram program = new PreviewProgram.Builder()                    .setChannelId(channelId)                    .setTitle(mp.getTitle())                    .setDescription(mp.getDescription())                    .setPosterArtUri(Uri.parse(mp.getCardImageUrl()))                    .setIntentUri(Uri.parse(SCHEME + "://" + APPS_LAUNCH_HOST                            + "/" + PLAY_MEDIA_ACTION_PATH + "/" + mediaProgramId))                    //.setPreviewVideoUri(Uri.parse(mp.getPreviewMediaUrl()))                    .setInternalProviderId(mediaProgramId)                    .setContentId(contentId)                    .setWeight(weight)                    .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)                    .build();            Uri programUri = context.getContentResolver().insert(PREVIEW_PROGRAMS_CONTENT_URI,                    program.toContentValues());            if (programUri == null || programUri.equals(Uri.EMPTY)) {                Log.e(TAG, "addChannel Insert program failed");            } else {                mp.setProgramId(ContentUris.parseId(programUri));            }        }        return channelId;    }    @WorkerThread    static void deleteChannel(Context context, long channelId) {        int rowsDeleted = context.getContentResolver().delete(                TvContractCompat.buildChannelUri(channelId), null, null);        if (rowsDeleted < 1) {            Log.e(TAG, "Delete channel failed");        }    }    @WorkerThread    public static void deleteProgram(Context context, MediaProgram program) {        deleteProgram(context, program.getProgramId());    }    @WorkerThread    static void deleteProgram(Context context, long programId) {        int rowsDeleted = context.getContentResolver().delete(                TvContractCompat.buildPreviewProgramUri(programId), null, null);        if (rowsDeleted < 1) {            Log.e(TAG, "Delete program failed");        }    }    /**     * Writes a drawable as the channel logo.     *     * @param channelId  identifies the channel to write the logo.     * @param drawableId resource to write as the channel logo. This must be a bitmap and not, say     *                   a vector drawable.     */    @WorkerThread    static private void writeChannelLogo(Context context, long channelId,                                         @DrawableRes int drawableId) {        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), drawableId);        ChannelLogoUtils.storeChannelLogo(context, channelId, bitmap);    }    @WorkerThread    static void updateMediaProgram(Context context, MediaProgram mediaProgram) {        long programId = mediaProgram.getProgramId();        Uri programUri = TvContractCompat.buildPreviewProgramUri(programId);        try (Cursor cursor = context.getContentResolver().query(programUri, null, null, null,                null)) {            if (!cursor.moveToFirst()) {                Log.e(TAG, "Update program failed");            }            PreviewProgram porgram = PreviewProgram.fromCursor(cursor);            PreviewProgram.Builder builder = new PreviewProgram.Builder(porgram)                    .setTitle(mediaProgram.getTitle());            int rowsUpdated = context.getContentResolver().update(programUri,                    builder.build().toContentValues(), null, null);            if (rowsUpdated < 1) {                Log.e(TAG, "Update program failed");            }        }    }    static void publishProgram(Context context, MediaProgram mediaProgram, long channelId, int weight) {        final String mediaProgramId = mediaProgram.getMediaProgramId();        PreviewProgram program = new PreviewProgram.Builder()                .setChannelId(channelId)                .setTitle(mediaProgram.getTitle())                .setDescription(mediaProgram.getDescription())                .setPosterArtUri(Uri.parse(mediaProgram.getCardImageUrl()))                .setIntentUri(Uri.parse(SCHEME + "://" + APPS_LAUNCH_HOST                        + "/" + PLAY_MEDIA_ACTION_PATH + "/" + mediaProgramId))                .setPreviewVideoUri(Uri.parse(mediaProgram.getPreviewMediaUrl()))                .setInternalProviderId(mediaProgramId)                .setWeight(weight)                .setType(TvContractCompat.PreviewPrograms.TYPE_MOVIE)                .build();        Uri programUri = context.getContentResolver().insert(PREVIEW_PROGRAMS_CONTENT_URI,                program.toContentValues());        if (programUri == null || programUri.equals(Uri.EMPTY)) {            Log.e(TAG, "Insert program failed");            return;        }        mediaProgram.setProgramId(ContentUris.parseId(programUri));    }    @WorkerThread    static void setProgramViewCount(Context context, long programId, int numberOfViews) {        Uri programUri = TvContractCompat.buildPreviewProgramUri(programId);        try (Cursor cursor = context.getContentResolver().query(programUri, null, null, null,                null)) {            if (!cursor.moveToFirst()) {                return;            }            PreviewProgram existingProgram = PreviewProgram.fromCursor(cursor);            PreviewProgram.Builder builder = new PreviewProgram.Builder(existingProgram)                    .setInteractionCount(numberOfViews)                    .setInteractionType(TvContractCompat.PreviewProgramColumns                            .INTERACTION_TYPE_VIEWS);            int rowsUpdated = context.getContentResolver().update(                    TvContractCompat.buildPreviewProgramUri(programId),                    builder.build().toContentValues(), null, null);            if (rowsUpdated != 1) {                Log.e(TAG, "Update program failed");            }        }    }}

二、节目背景显示本地目录的图片

对于显示本地图片,需要使用FileProvider 获取图片文件的uri然后设置给节目。如果使用 Uri.fromFile(new File(filePath) 这种方式,就会报Permission问题:
class java.io.FileNotFoundException: /storage/emulated/0/Pictures/mediachannel/usb_thumbnail.jpg (Permission denied)

使用FileProvider分享文件给其他应用需要给对应的应用赋予读权限,可以通过如下两种方式:

 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); grantUriPermissionToApp("com.google.android.tvlauncher", usbUri);

这里只能使用第二种方式了。com.google.android.tvlauncher 为TV launcher的包名。

1. 使用FileProvider首先需要在AndroidManifest.xml 节点下申明

                

2. 在res下xml文件夹下新建filepaths.xml

<?xml version="1.0" encoding="utf-8"?>    

3. 获取文件URI

File file = new File(sdPath + "/Pictures/mediachannel/usb_thumbnail.jpg");Uri uri = FileProvider.getUriForFile(this, "com.rogera.mediaplaychannel.fileprovider", file);

三、其他说明

1、sdcard里面的文件是push进去的,是假设应用获取U盘里面的电影/图片/音乐 生成的缩略图。点击桌面的usb节目就会播放相应的电影/图片/音乐。

2. 在桌面添加频道、节目需要申请EPG权限,SD需要申请storage权限

3. 同时需要在gradle添加如下依赖

implementation 'com.android.support:leanback-v17:26.1.0'implementation 'com.android.support:support-tv-provider:26.1.0'

4. 上图啦

Google官方参考:https://developer.android.google.cn/training/tv/tif/channel.html#update

更多相关文章

  1. Android(安卓)Studio V3.12环境下TV开发教程(十)添加引导步骤
  2. Android(安卓)添加数据到本地Excel表中
  3. Android(安卓)studio 2.2 JNI ffmpeg 简单的播放器(这里只有视频
  4. 【Android(安卓)Studio 4.0.0】高版本Android(安卓)Studio 打开
  5. 添加android系统通知
  6. android中如何添加一个监听按钮,点击之后从一个activity跳转到另
  7. Window和WindowManager(Android开发艺术探索学习笔记)
  8. Android动态添加布局LayoutInflater简单用法
  9. Android(安卓)NDK 编译过程中遇到错误 exception handling disab

随机推荐

  1. Android开发之fedora13下编译Android源码
  2. Android mac 真机调试
  3. Android菜鸟日记29-杂记
  4. Android 设备监听网络状态变化
  5. Android使用动画的注意事项
  6. Android Drawable 那些不为人知的高效用
  7. 浅谈android系统java调用C++函数传参过程
  8. LoaderManager - Android(安卓)3.0新特性
  9. Android UI结构源码研究
  10. eclipse使用appcompat_v7库无法找到andro