หน้าจอหลักของ Android TV มี UI ที่แสดงเนื้อหาแนะนำเป็นตาราง ช่องและ โปรแกรม โดยแต่ละแถวคือช่อง และช่องจะมีบัตรสำหรับทุกโปรแกรมที่พร้อมให้บริการในช่องนั้นๆ ดังนี้
เอกสารนี้จะแสดงวิธีเพิ่มช่องและโปรแกรมลงในหน้าจอหลัก อัปเดตเนื้อหา จัดการการดำเนินการของผู้ใช้ และมอบประสบการณ์การใช้งานที่ดีที่สุดแก่ผู้ใช้ (หากต้องการเจาะลึกเกี่ยวกับ API ให้ลองทำตาม Codelab หน้าจอหลัก และดูเซสชัน I/O 2017 Android TV)
UI ของหน้าจอหลัก
แอปสามารถสร้างช่องใหม่ เพิ่ม นำออก และอัปเดตโปรแกรมในช่อง รวมถึงควบคุมลำดับของโปรแกรมในช่องได้ เช่น แอปสามารถสร้างช่องชื่อ "มีอะไรใหม่" และแสดงบัตรสำหรับโปรแกรมที่เพิ่งพร้อมให้บริการ
แอปไม่สามารถควบคุมลำดับที่ช่องปรากฏในหน้าจอหลัก เมื่อแอปสร้างช่องใหม่ หน้าจอหลักจะเพิ่มช่องนั้นลงในส่วนท้ายของรายการช่อง ผู้ใช้สามารถจัดเรียงใหม่ ซ่อน และแสดงช่องได้
ช่อง "แนะนำให้รับชม"
ช่อง "แนะนำให้รับชม" เป็นแถวที่ 2 ที่ปรากฏในหน้าจอหลักหลังจากแถวแอป ระบบจะสร้างและดูแลช่องนี้ แอปของคุณสามารถเพิ่มโปรแกรมลงในช่อง "แนะนำให้รับชม" ได้ ดูข้อมูลเพิ่มเติมได้ที่หัวข้อ เพิ่มโปรแกรมลงใน ช่อง "แนะนำให้รับชม"
ช่องของแอป
ช่องที่แอปของคุณสร้างขึ้นทั้งหมดจะเป็นไปตามวงจรชีวิตต่อไปนี้
- ผู้ใช้ค้นพบช่องในแอปและขอเพิ่มช่องนั้นลงในหน้าจอหลัก
- แอปสร้างช่องและเพิ่มช่องนั้นลงใน
TvProvider(ณ จุดนี้ ช่องจะยังไม่ปรากฏ) - แอปขอให้ระบบแสดงช่อง
- ระบบขอให้ผู้ใช้อนุมัติช่องใหม่
- ช่องใหม่จะปรากฏในแถวสุดท้ายของหน้าจอหลัก
ช่องเริ่มต้น
แอปของคุณสามารถเสนอช่องจำนวนเท่าใดก็ได้ให้ผู้ใช้เพิ่มลงในหน้าจอหลัก โดยปกติผู้ใช้จะต้องเลือกและอนุมัติแต่ละช่องก่อนที่ช่องจะปรากฏในหน้าจอหลัก ทุกแอปมีตัวเลือกในการสร้างช่อง เริ่มต้น 1 ช่อง ช่องเริ่มต้นมีความพิเศษตรงที่จะปรากฏในหน้าจอหลักโดยอัตโนมัติ ผู้ใช้จึงไม่ต้องขออย่างชัดแจ้ง
สิ่งที่ต้องมีก่อน
หน้าจอหลักของ Android TV ใช้ API TvProvider ของ Android เพื่อจัดการช่องและโปรแกรมที่แอปของคุณสร้างขึ้น หากต้องการเข้าถึงข้อมูลของผู้ให้บริการ ให้เพิ่มสิทธิ์ต่อไปนี้ลงในไฟล์ Manifest ของแอป
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
ไลบรารีการสนับสนุน TvProvider ช่วยให้ใช้งานผู้ให้บริการได้ง่ายขึ้น เพิ่มไลบรารีนี้ลงในทรัพยากร Dependency ในไฟล์ build.gradle ดังนี้
ดึงดูด
implementation 'androidx.tvprovider:tvprovider:1.0.0'
Kotlin
implementation("androidx.tvprovider:tvprovider:1.0.0")
หากต้องการใช้ช่องและโปรแกรม โปรดตรวจสอบว่าได้รวมการนำเข้าไลบรารีการสนับสนุนเหล่านี้ไว้ในโปรแกรมแล้ว
Kotlin
import android.support.media.tv.Channel
import android.support.media.tv.TvContractCompat
import android.support.media.tv.ChannelLogoUtils
import android.support.media.tv.PreviewProgram
import android.support.media.tv.WatchNextProgram
Java
import android.support.media.tv.Channel;
import android.support.media.tv.TvContractCompat;
import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.PreviewProgram;
import android.support.media.tv.WatchNextProgram;
ช่อง
ช่องแรกที่แอปของคุณสร้างขึ้นจะกลายเป็นช่องเริ่มต้นของแอป ช่องเริ่มต้นจะปรากฏในหน้าจอหลักโดยอัตโนมัติ ส่วนช่องอื่นๆ ที่คุณสร้างขึ้น ผู้ใช้ต้องเลือกและยอมรับก่อนช่องจึงจะปรากฏในหน้าจอหลักได้
การสร้างช่อง
แอปของคุณควรร้องขอให้ระบบแสดงช่องที่เพิ่งเพิ่มใหม่เมื่อแอปทำงานอยู่เบื้องหน้าเท่านั้น วิธีนี้จะป้องกันไม่ให้แอปแสดงกล่องโต้ตอบที่ขออนุมัติเพิ่มช่องขณะที่ผู้ใช้กำลังใช้งานแอปอื่น หากคุณพยายามเพิ่มช่องขณะที่แอปทำงานอยู่เบื้องหลัง เมธอด onActivityResult() ของกิจกรรมจะแสดงรหัสสถานะ RESULT_CANCELED
หากต้องการสร้างช่อง ให้ทำตามขั้นตอนต่อไปนี้
สร้างเครื่องมือสร้างช่องและตั้งค่าแอตทริบิวต์ โปรดทราบว่าประเภทช่องต้องเป็น
TYPE_PREVIEWและเพิ่มแอตทริบิวต์อื่นๆ ตามที่จำเป็นKotlin
val builder = Channel.Builder() // Every channel you create must have the type `TYPE_PREVIEW` builder.setType(TvContractCompat.Channels.TYPE_PREVIEW) .setDisplayName("Channel Name") .setAppLinkIntentUri(uri)Java
Channel.Builder builder = new Channel.Builder(); // Every channel you create must have the type `TYPE_PREVIEW` builder.setType(TvContractCompat.Channels.TYPE_PREVIEW) .setDisplayName("Channel Name") .setAppLinkIntentUri(uri);แทรกช่องลงในผู้ให้บริการ
Kotlin
var channelUri = context.contentResolver.insert( TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues())Java
Uri channelUri = context.getContentResolver().insert( TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues());คุณต้องบันทึกรหัสช่องเพื่อเพิ่มโปรแกรมลงในช่องในภายหลัง โดยแยกวิเคราะห์รหัสช่องจาก URI ที่แสดงผล
Kotlin
var channelId = ContentUris.parseId(channelUri)Java
long channelId = ContentUris.parseId(channelUri);คุณต้องเพิ่มโลโก้สำหรับช่อง โดยใช้
UriหรือBitmapไอคอนโลโก้ควรมีขนาด 80dp x 80dp และต้องทึบแสง โดยจะแสดงภายใต้มาสก์วงกลมดังนี้
Kotlin
// Choose one or the other storeChannelLogo(context: Context, channelId: Long, logoUri: Uri) // also works if logoUri is a URL storeChannelLogo(context: Context, channelId: Long, logo: Bitmap)Java
// Choose one or the other storeChannelLogo(Context context, long channelId, Uri logoUri); // also works if logoUri is a URL storeChannelLogo(Context context, long channelId, Bitmap logo);สร้างช่องเริ่มต้น (ไม่บังคับ): เมื่อแอปสร้างช่องแรก คุณสามารถกำหนดให้ช่องนั้นเป็นช่อง ช่องเริ่มต้น เพื่อให้ช่องปรากฏในหน้าจอหลัก ทันทีโดยไม่ต้องให้ผู้ใช้ดำเนินการใดๆ ส่วนช่องอื่นๆ ที่คุณสร้างขึ้น จะไม่ปรากฏจนกว่าผู้ใช้จะเลือกช่องเหล่านั้นอย่างชัดแจ้ง selects
Kotlin
TvContractCompat.requestChannelBrowsable(context, channelId)Java
TvContractCompat.requestChannelBrowsable(context, channelId);ทำให้ช่องเริ่มต้นปรากฏก่อนที่จะเปิดแอป คุณสามารถทำให้เกิดลักษณะการทำงานนี้ได้โดยการเพิ่ม
BroadcastReceiverที่รอรับการดำเนินการandroid.media.tv.action.INITIALIZE_PROGRAMSซึ่งหน้าจอหลักจะส่งหลังจากติดตั้งแอปแล้ว<receiver android:name=".RunOnInstallReceiver" android:exported="true"> <intent-filter> <action android:name="android.media.tv.action.INITIALIZE_PROGRAMS" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver>เมื่อโหลดแอปจากแหล่งที่ไม่รู้จักระหว่างการพัฒนา คุณสามารถทดสอบขั้นตอนนี้ได้โดยการ ทริกเกอร์ Intent ผ่าน adb โดยที่ your.package.name/.YourReceiverName คือ
BroadcastReceiverของแอปadb shell am broadcast -a android.media.tv.action.INITIALIZE_PROGRAMS -n \ your.package.name/.YourReceiverNameในบางกรณีที่พบได้ยาก แอปของคุณอาจได้รับการออกอากาศในเวลาเดียวกับที่ผู้ใช้เริ่มแอป โปรดตรวจสอบว่าโค้ดของคุณไม่ได้พยายามเพิ่มช่องเริ่มต้นมากกว่า 1 ครั้ง
การอัปเดตช่อง
การอัปเดตช่องคล้ายกับการสร้างช่องมาก
ใช้ Channel.Builder อีกรายการเพื่อตั้งค่าแอตทริบิวต์ที่ต้องเปลี่ยน
ใช้ ContentResolver เพื่ออัปเดตช่อง โดยใช้รหัสช่องที่คุณบันทึกไว้เมื่อเพิ่มช่องครั้งแรก
Kotlin
context.contentResolver.update(
TvContractCompat.buildChannelUri(channelId),
builder.build().toContentValues(),
null,
null
)
Java
context.getContentResolver().update(TvContractCompat.buildChannelUri(channelId),
builder.build().toContentValues(), null, null);
หากต้องการอัปเดตโลโก้ของช่อง ให้ใช้ storeChannelLogo()
การลบช่อง
Kotlin
context.contentResolver.delete(TvContractCompat.buildChannelUri(channelId), null, null)
Java
context.getContentResolver().delete(TvContractCompat.buildChannelUri(channelId), null, null);
โปรแกรม
โปรแกรมคือบัตรเนื้อหาแต่ละรายการที่แสดงภายในช่อง คุณสามารถเผยแพร่โปรแกรมไปยังทั้งช่องที่กำหนดเองของแอปและช่อง "แนะนำให้รับชม" ที่ระบบจัดการ
การเพิ่มโปรแกรมลงในช่องของแอป
สร้าง PreviewProgram.Builder และตั้งค่าแอตทริบิวต์
Kotlin
val builder = PreviewProgram.Builder()
builder.setChannelId(channelId)
.setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
.setTitle("Title")
.setDescription("Program description")
.setPosterArtUri(uri)
.setIntentUri(uri)
.setInternalProviderId(appProgramId)
Java
PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(channelId)
.setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
.setTitle("Title")
.setDescription("Program description")
.setPosterArtUri(uri)
.setIntentUri(uri)
.setInternalProviderId(appProgramId);
เพิ่มแอตทริบิวต์อื่นๆ ขึ้นอยู่กับประเภทของโปรแกรม (หากต้องการดูแอตทริบิวต์ ที่ใช้ได้สำหรับโปรแกรมแต่ละประเภท โปรดดู ตาราง ด้านล่าง)
แทรกโปรแกรมลงในผู้ให้บริการ
Kotlin
var programUri = context.contentResolver.insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
builder.build().toContentValues())
Java
Uri programUri = context.getContentResolver().insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
builder.build().toContentValues());
ดึงข้อมูลรหัสโปรแกรมไว้ใช้อ้างอิงในภายหลัง
Kotlin
val programId = ContentUris.parseId(programUri)
Java
long programId = ContentUris.parseId(programUri);
การเพิ่มโปรแกรมลงในช่อง "แนะนำให้รับชม"
หากต้องการแทรกโปรแกรมลงในช่อง "แนะนำให้รับชม" โปรดดูหัวข้อเพิ่มโปรแกรมลงในช่อง แนะนำให้รับชม
การอัปเดตโปรแกรม
คุณสามารถเปลี่ยนข้อมูลของโปรแกรมได้ เช่น คุณอาจต้องการอัปเดตราคาเช่าภาพยนตร์ หรืออัปเดตแถบความคืบหน้าที่แสดงจำนวนโปรแกรมที่ผู้ใช้ดูไปแล้ว
ใช้ PreviewProgram.Builder เพื่อตั้งค่าแอตทริบิวต์ที่ต้องการเปลี่ยน จากนั้นเรียกใช้ getContentResolver().update เพื่ออัปเดตโปรแกรม โดยระบุรหัสโปรแกรมที่คุณบันทึกไว้เมื่อเพิ่มโปรแกรมครั้งแรก
Kotlin
context.contentResolver.update(
TvContractCompat.buildPreviewProgramUri(programId),
builder.build().toContentValues(), null, null
)
Java
context.getContentResolver().update(TvContractCompat.buildPreviewProgramUri(programId),
builder.build().toContentValues(), null, null);
การลบโปรแกรม
Kotlin
context.contentResolver
.delete(TvContractCompat.buildPreviewProgramUri(programId), null, null)
Java
context.getContentResolver().delete(TvContractCompat.buildPreviewProgramUri(programId), null, null);
การจัดการการดำเนินการของผู้ใช้
แอปของคุณสามารถช่วยให้ผู้ใช้ค้นพบเนื้อหาได้โดยการแสดง UI เพื่อแสดงและเพิ่มช่อง นอกจากนี้ แอปควรจัดการการโต้ตอบกับช่องหลังจากที่ช่องปรากฏในหน้าจอหลักแล้วด้วย
การค้นพบและการเพิ่มช่อง
แอปของคุณสามารถแสดงองค์ประกอบ UI ที่ช่วยให้ผู้ใช้เลือกและเพิ่มช่องของแอปได้ (เช่น ปุ่มที่ขอให้เพิ่มช่อง)
หลังจากที่ผู้ใช้ขอช่องที่เฉพาะเจาะจงแล้ว ให้เรียกใช้โค้ดนี้เพื่อขอสิทธิ์จากผู้ใช้ในการเพิ่มช่องลงใน UI ของหน้าจอหลัก
Kotlin
val intent = Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE)
intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId)
try {
activity.startActivityForResult(intent, 0)
} catch (e: ActivityNotFoundException) {
// handle error
}
Java
Intent intent = new Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE);
intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
try {
activity.startActivityForResult(intent, 0);
} catch (ActivityNotFoundException e) {
// handle error
}
ระบบจะแสดงกล่องโต้ตอบที่ขอให้ผู้ใช้อนุมัติช่อง จัดการผลลัพธ์ของคำขอในเมธอด onActivityResult ของกิจกรรม (Activity.RESULT_CANCELED หรือ Activity.RESULT_OK)
เหตุการณ์ในหน้าจอหลักของ Android TV
เมื่อผู้ใช้โต้ตอบกับโปรแกรมและช่องที่แอปเผยแพร่ หน้าจอหลักจะส่ง Intent ไปยังแอปดังนี้
- หน้าจอหลักจะส่ง
Uriที่จัดเก็บไว้ในแอตทริบิวต์ APP_LINK_INTENT_URI ของช่องไปยังแอปเมื่อผู้ใช้เลือกโลโก้ของช่อง แอปควรเปิด UI หลักหรือมุมมองที่เกี่ยวข้องกับช่องที่เลือก - หน้าจอหลักจะส่ง
Uriที่จัดเก็บไว้ในแอตทริบิวต์ INTENT_URI ของโปรแกรมไปยังแอปเมื่อผู้ใช้เลือกโปรแกรม แอปควรเล่นเนื้อหาที่เลือก - ผู้ใช้สามารถระบุว่าไม่สนใจโปรแกรมอีกต่อไปและต้องการนำโปรแกรมออกจาก UI ของหน้าจอหลัก ระบบจะนำโปรแกรมออกจาก UI และส่ง Intent (android.media.tv.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED หรือ android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED) พร้อมรหัสของโปรแกรมไปยังแอปที่เป็นเจ้าของโปรแกรม แอปควรนำโปรแกรมออกจากผู้ให้บริการและไม่ควรแทรกโปรแกรมกลับเข้าไปอีก
อย่าลืมสร้างตัวกรอง Intent สำหรับ Uris ทั้งหมดที่หน้าจอหลัก
ส่งสำหรับการโต้ตอบของผู้ใช้ เช่น
<receiver
android:name=".WatchNextProgramRemoved"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
</intent-filter>
</receiver>
ข้อพิจารณาเพิ่มเติม
- แอปทีวีหลายแอปกำหนดให้ผู้ใช้ต้องเข้าสู่ระบบ ในกรณีนี้
BroadcastReceiverที่รอรับandroid.media.tv.action.INITIALIZE_PROGRAMSควรแนะนำเนื้อหาช่องสำหรับผู้ใช้ที่ไม่ได้ตรวจสอบสิทธิ์ เช่น แอปของคุณสามารถแสดงเนื้อหาที่ดีที่สุดหรือเนื้อหาที่กำลังได้รับความนิยมในปัจจุบันได้ในตอนแรก หลังจากที่ผู้ใช้เข้าสู่ระบบแล้ว แอปจะแสดงเนื้อหาที่ปรับตามโปรไฟล์ของผู้ใช้ได้ นี่เป็นโอกาสที่ดีสำหรับแอปในการเสนอขายสินค้าและบริการเพิ่มเติมแก่ผู้ใช้ก่อนที่ผู้ใช้จะเข้าสู่ระบบ - เมื่อแอปไม่ได้ทำงานอยู่เบื้องหน้าและคุณต้องอัปเดตช่องหรือโปรแกรม
ให้ใช้
JobSchedulerเพื่อกำหนดเวลาการทำงาน (ดูJobSchedulerและJobService) - ระบบสามารถเพิกถอนสิทธิ์ของผู้ให้บริการของแอปได้หากแอปทำงานไม่ถูกต้อง (เช่น สแปมข้อมูลไปยังผู้ให้บริการอย่างต่อเนื่อง) โปรดตรวจสอบว่าคุณได้ใส่โค้ดที่เข้าถึงผู้ให้บริการไว้ในคําสั่ง try-catch เพื่อจัดการข้อยกเว้นด้านความปลอดภัย
ก่อนอัปเดตโปรแกรมและช่อง ให้ค้นหาข้อมูลที่ต้องการอัปเดตจากผู้ให้บริการและปรับข้อมูลให้ตรงกัน เช่น ไม่จำเป็นต้องอัปเดตโปรแกรมที่ผู้ใช้ต้องการนำออกจาก UI ใช้การทำงานเบื้องหลังที่แทรกหรืออัปเดตข้อมูลลงในผู้ให้บริการหลังจากค้นหาข้อมูลที่มีอยู่แล้ว จากนั้นขออนุมัติช่อง คุณสามารถเรียกใช้การทำงานนี้เมื่อแอปเริ่มต้นและเมื่อใดก็ตามที่แอปต้องอัปเดตข้อมูล
Kotlin
context.contentResolver
.query(
TvContractCompat.buildChannelUri(channelId),
null, null, null, null).use({
cursor-> if (cursor != null and cursor.moveToNext()) {
val channel = Channel.fromCursor(cursor)
if (channel.isBrowsable()) {
//update channel's programs
}
}
})
Java
try (Cursor cursor = context.getContentResolver()
.query(
TvContractCompat.buildChannelUri(channelId),
null,
null,
null,
null)) {
if (cursor != null && cursor.moveToNext()) {
Channel channel = Channel.fromCursor(cursor);
if (channel.isBrowsable()) {
//update channel's programs
}
}
}
ใช้ Uri ที่ไม่ซ้ำกันสำหรับรูปภาพทั้งหมด (โลโก้ ไอคอน รูปภาพเนื้อหา) และอย่าลืมใช้ Uri อื่นเมื่ออัปเดตรูปภาพ ระบบจะแคชรูปภาพทั้งหมด หากคุณไม่เปลี่ยน Uri เมื่อเปลี่ยนรูปภาพ รูปภาพเก่าจะยังคงปรากฏอยู่
โปรดทราบว่าไม่อนุญาตให้ใช้คําสั่ง WHERE และการเรียกผู้ให้บริการด้วยคําสั่ง WHERE จะทำให้เกิดข้อยกเว้นด้านความปลอดภัย
แอตทริบิวต์
ส่วนนี้จะอธิบายแอตทริบิวต์ของช่องและโปรแกรมแยกกัน
แอตทริบิวต์ของช่อง
คุณต้องระบุแอตทริบิวต์ต่อไปนี้สำหรับทุกช่อง
| แอตทริบิวต์ | หมายเหตุ |
|---|---|
| ประเภท | ตั้งค่าเป็น TYPE_PREVIEW |
| DISPLAY_NAME | ตั้งค่าเป็นชื่อของช่อง |
| APP_LINK_INTENT_URI | เมื่อผู้ใช้เลือกโลโก้ของช่อง ระบบจะส่ง Intent เพื่อเริ่มกิจกรรมที่แสดงเนื้อหาที่เกี่ยวข้องกับช่อง ตั้งค่าแอตทริบิวต์นี้เป็น Uri ที่ใช้ในตัวกรอง Intent สำหรับกิจกรรมนั้น |
นอกจากนี้ ช่องยังมีช่องที่สงวนไว้ 6 ช่องสำหรับการใช้งานแอปภายใน คุณสามารถใช้ช่องเหล่านี้เพื่อจัดเก็บคีย์หรือค่าอื่นๆ ที่ช่วยให้แอปจับคู่ช่องกับโครงสร้างข้อมูลภายในได้
- INTERNAL_PROVIDER_ID
- INTERNAL_PROVIDER_DATA
- INTERNAL_PROVIDER_FLAG1
- INTERNAL_PROVIDER_FLAG2
- INTERNAL_PROVIDER_FLAG3
- INTERNAL_PROVIDER_FLAG4
แอตทริบิวต์ของโปรแกรม
ดูหน้าแต่ละหน้าสำหรับแอตทริบิวต์ของโปรแกรมแต่ละประเภท
- แอตทริบิวต์ของโปรแกรมวิดีโอ
- แอตทริบิวต์ของโปรแกรมเสียง
- แอตทริบิวต์ของโปรแกรมเกม
- แอตทริบิวต์ของโปรแกรม "แนะนำให้รับชม"
โค้ดตัวอย่าง
ดูข้อมูลเพิ่มเติมเกี่ยวกับการสร้างแอปที่โต้ตอบกับหน้าจอหลัก รวมถึงการเพิ่ม ช่องและโปรแกรมลงในหน้าจอหลักของ Android TV ได้ที่ Codelab หน้าจอหลัก