TV uygulamalarını aranabilir hale getirme

Android TV, yüklü uygulamalardan içerik verilerini almak ve arama sonuçlarını kullanıcıya sunmak için Android arama arayüzünü kullanır. Kullanıcının uygulamanızdaki içeriğe anında erişebilmesi için uygulamanızın içerik verileri bu sonuçlara dahil edilebilir.

Kullanıcı, arama iletişim kutusuna karakterleri girerken, uygulamanızın Android TV'ye Android TV'nin önerilen arama sonuçlarını oluşturabileceği veri alanlarını sağlaması gerekir. Bunun için uygulamanızın, önerileri sunan bir İçerik Sağlayıcı uygulaması gerekir. İçerik sağlayıcıyı ve Android TV için diğer önemli bilgileri açıklayan searchable.xml yapılandırma dosyası bu dosyayı sunar. Ayrıca, kullanıcı önerilen bir arama sonucunu seçtiğinde tetiklenen amacı işleyen bir etkinliğe de ihtiyacınız vardır. Daha fazla bilgi için Özel arama önerileri ekleme bölümüne bakın. Bu kılavuzda, Android TV uygulamalarına özgü ana noktalar ele alınmaktadır.

Bu kılavuzu okumadan önce, Search API kılavuzunda açıklanan kavramları bildiğinizden emin olun. Ayrıca, Arama işlevi ekleme makalesini inceleyin.

Bu kılavuzdaki örnek kod, Leanback örnek uygulamasından gelmektedir.

Sütunları tanımlama

SearchManager, beklediği veri alanlarını yerel bir veritabanının sütunları olarak temsil ederek açıklar. Verilerinizin biçiminden bağımsız olarak, veri alanlarınızı bu sütunlarla (genellikle içerik verilerinize erişen sınıfta) eşlemeniz gerekir. Mevcut verilerinizi gerekli alanlarla eşleyen bir sınıf oluşturma hakkında bilgi edinmek için Öneri tablosu oluşturma'ya göz atın.

SearchManager sınıfı, Android TV için birkaç sütun içerir. Daha önemli sütunlardan bazıları aşağıdaki tabloda açıklanmıştır.

Değer Açıklama
SUGGEST_COLUMN_TEXT_1 İçeriğinizin adı (zorunlu)
SUGGEST_COLUMN_TEXT_2 İçeriğinizin metin açıklaması
SUGGEST_COLUMN_RESULT_CARD_IMAGE İçeriğiniz için bir resim, poster veya kapak
SUGGEST_COLUMN_CONTENT_TYPE Medyanızın MIME türü
SUGGEST_COLUMN_VIDEO_WIDTH Medyanızın çözünürlük genişliği
SUGGEST_COLUMN_VIDEO_HEIGHT Medyanızın çözünürlük yüksekliği
SUGGEST_COLUMN_PRODUCTION_YEAR İçeriğinizin üretim yılı (zorunlu)
SUGGEST_COLUMN_DURATION Medyanızın milisaniye cinsinden süresi (gerekli)

Arama çerçevesi için aşağıdaki sütunlar gereklidir:

İçeriğinize yönelik bu sütunların değerleri, Google sunucuları tarafından bulunan diğer sağlayıcılardan alınan aynı içeriğin değerleriyle eşleştiğinde, sistem içeriğin ayrıntılar görünümünde, diğer sağlayıcıların uygulamalarına bağlantılarla birlikte, uygulamanıza giden bir derin bağlantı sağlar. Bu konu, Ayrıntılar ekranında uygulamanızın derin bağlantısı bölümünde daha ayrıntılı ele alınmaktadır.

Uygulamanızın veritabanı sınıfı, sütunları şu şekilde tanımlayabilir:

Kotlin

class VideoDatabase {
    companion object {
        // The columns we'll include in the video database table
        val KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1
        val KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2
        val KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE
        val KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE
        val KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE
        val KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH
        val KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT
        val KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG
        val KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE
        val KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE
        val KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE
        val KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE
        val KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR
        val KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION
        val KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION
        ...
    }
    ...
}

Java

public class VideoDatabase {
    // The columns we'll include in the video database table
    public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
    public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2;
    public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE;
    public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE;
    public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE;
    public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH;
    public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT;
    public static final String KEY_AUDIO_CHANNEL_CONFIG =
            SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG;
    public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE;
    public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE;
    public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE;
    public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE;
    public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR;
    public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION;
    public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION;
...

SearchManager sütunlarından veri alanlarınıza harita oluştururken her satıra benzersiz bir kimlik vermek için _ID özelliğini de belirtmeniz gerekir.

Kotlin


companion object {
    ....
    private fun buildColumnMap(): Map<String, String> {
        return mapOf(
          KEY_NAME to KEY_NAME,
          KEY_DESCRIPTION to KEY_DESCRIPTION,
          KEY_ICON to KEY_ICON,
          KEY_DATA_TYPE to KEY_DATA_TYPE,
          KEY_IS_LIVE to KEY_IS_LIVE,
          KEY_VIDEO_WIDTH to KEY_VIDEO_WIDTH,
          KEY_VIDEO_HEIGHT to KEY_VIDEO_HEIGHT,
          KEY_AUDIO_CHANNEL_CONFIG to KEY_AUDIO_CHANNEL_CONFIG,
          KEY_PURCHASE_PRICE to KEY_PURCHASE_PRICE,
          KEY_RENTAL_PRICE to KEY_RENTAL_PRICE,
          KEY_RATING_STYLE to KEY_RATING_STYLE,
          KEY_RATING_SCORE to KEY_RATING_SCORE,
          KEY_PRODUCTION_YEAR to KEY_PRODUCTION_YEAR,
          KEY_COLUMN_DURATION to KEY_COLUMN_DURATION,
          KEY_ACTION to KEY_ACTION,
          BaseColumns._ID to ("rowid AS " + BaseColumns._ID),
          SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID),
          SearchManager.SUGGEST_COLUMN_SHORTCUT_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID)
        )
    }
}

Java

...
  private static HashMap<String, String> buildColumnMap() {
    HashMap<String, String> map = new HashMap<String, String>();
    map.put(KEY_NAME, KEY_NAME);
    map.put(KEY_DESCRIPTION, KEY_DESCRIPTION);
    map.put(KEY_ICON, KEY_ICON);
    map.put(KEY_DATA_TYPE, KEY_DATA_TYPE);
    map.put(KEY_IS_LIVE, KEY_IS_LIVE);
    map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH);
    map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT);
    map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG);
    map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE);
    map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE);
    map.put(KEY_RATING_STYLE, KEY_RATING_STYLE);
    map.put(KEY_RATING_SCORE, KEY_RATING_SCORE);
    map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR);
    map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION);
    map.put(KEY_ACTION, KEY_ACTION);
    map.put(BaseColumns._ID, "rowid AS " +
            BaseColumns._ID);
    map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
    map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
            SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
    return map;
  }
...

Yukarıdaki örnekte, SUGGEST_COLUMN_INTENT_DATA_ID alanıyla eşlemeye dikkat edin. Bu, URI'nın bu satırdaki verilere özgü içeriğe işaret eden kısmıdır. URI'nın, içeriğin nerede depolandığını açıklayan son kısmıdır. Tablodaki tüm satırlar için ortak olan URI'nın ilk bölümü, Arama önerilerini işleme bölümünde açıklandığı gibi searchable.xml dosyasında android:searchSuggestIntentData özelliği olarak ayarlanır.

URI'nın ilk bölümü tablodaki her satır için farklıysa bu değeri SUGGEST_COLUMN_INTENT_DATA alanıyla eşleyin. Kullanıcı bu içeriği seçtiğinde etkinleşen intent, SUGGEST_COLUMN_INTENT_DATA_ID ile android:searchSuggestIntentData özelliği veya SUGGEST_COLUMN_INTENT_DATA alan değerinin kombinasyonundan elde edilen intent verilerini sağlar.

Arama önerisi verileri sağlama

Android TV arama iletişim kutusuna arama terimi önerileri döndürmek için İçerik Sağlayıcı uygulayın. Sistem, her yazı yazıldığında query() yöntemini çağırarak içerik sağlayıcınızı öneriler için sorgular. query() uygulamanızda, içerik sağlayıcınız öneri verilerinizi arar ve öneriler için belirttiğiniz satırlara işaret eden bir Cursor döndürür.

Kotlin

fun query(uri: Uri, projection: Array<String>, selection: String, selectionArgs: Array<String>,
        sortOrder: String): Cursor {
    // Use the UriMatcher to see what kind of query we have and format the db query accordingly
    when (URI_MATCHER.match(uri)) {
        SEARCH_SUGGEST -> {
            Log.d(TAG, "search suggest: ${selectionArgs[0]} URI: $uri")
            if (selectionArgs == null) {
                throw IllegalArgumentException(
                        "selectionArgs must be provided for the Uri: $uri")
            }
            return getSuggestions(selectionArgs[0])
        }
        else -> throw IllegalArgumentException("Unknown Uri: $uri")
    }
}

private fun getSuggestions(query: String): Cursor {
    val columns = arrayOf<String>(
            BaseColumns._ID,
            VideoDatabase.KEY_NAME,
            VideoDatabase.KEY_DESCRIPTION,
            VideoDatabase.KEY_ICON,
            VideoDatabase.KEY_DATA_TYPE,
            VideoDatabase.KEY_IS_LIVE,
            VideoDatabase.KEY_VIDEO_WIDTH,
            VideoDatabase.KEY_VIDEO_HEIGHT,
            VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG,
            VideoDatabase.KEY_PURCHASE_PRICE,
            VideoDatabase.KEY_RENTAL_PRICE,
            VideoDatabase.KEY_RATING_STYLE,
            VideoDatabase.KEY_RATING_SCORE,
            VideoDatabase.KEY_PRODUCTION_YEAR,
            VideoDatabase.KEY_COLUMN_DURATION,
            VideoDatabase.KEY_ACTION,
            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
    )
    return videoDatabase.getWordMatch(query.toLowerCase(), columns)
}

Java

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
        String sortOrder) {
    // Use the UriMatcher to see what kind of query we have and format the db query accordingly
    switch (URI_MATCHER.match(uri)) {
        case SEARCH_SUGGEST:
            Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri);
            if (selectionArgs == null) {
                throw new IllegalArgumentException(
                        "selectionArgs must be provided for the Uri: " + uri);
            }
            return getSuggestions(selectionArgs[0]);
        default:
            throw new IllegalArgumentException("Unknown Uri: " + uri);
    }
}

private Cursor getSuggestions(String query) {
    query = query.toLowerCase();
    String[] columns = new String[]{
        BaseColumns._ID,
        VideoDatabase.KEY_NAME,
        VideoDatabase.KEY_DESCRIPTION,
        VideoDatabase.KEY_ICON,
        VideoDatabase.KEY_DATA_TYPE,
        VideoDatabase.KEY_IS_LIVE,
        VideoDatabase.KEY_VIDEO_WIDTH,
        VideoDatabase.KEY_VIDEO_HEIGHT,
        VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG,
        VideoDatabase.KEY_PURCHASE_PRICE,
        VideoDatabase.KEY_RENTAL_PRICE,
        VideoDatabase.KEY_RATING_STYLE,
        VideoDatabase.KEY_RATING_SCORE,
        VideoDatabase.KEY_PRODUCTION_YEAR,
        VideoDatabase.KEY_COLUMN_DURATION,
        VideoDatabase.KEY_ACTION,
        SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
    };
    return videoDatabase.getWordMatch(query, columns);
}
...

Manifest dosyanızda, içerik sağlayıcıya özel muamelede bulunuluyor. Etkinlik olarak etiketlenmek yerine <provider> olarak tanımlanır. Sağlayıcı, sisteme içerik sağlayıcınızın ad alanını bildirmek için android:authorities özelliğini ekler. Ayrıca, Android genel aramasının döndürülen sonuçları kullanabilmesi için android:exported özelliğini "true" olarak ayarlamanız gerekir.

<provider android:name="com.example.android.tvleanback.VideoContentProvider"
    android:authorities="com.example.android.tvleanback"
    android:exported="true" />

Arama önerilerini işleme

Arama önerileri ayarlarını yapılandırmak için uygulamanızın res/xml/searchable.xml dosyası içermesi gerekir.

Sisteme, içerik sağlayıcınızın ad alanını bildirmek için res/xml/searchable.xml dosyasına android:searchSuggestAuthority özelliğini ekleyin. Bu değer, AndroidManifest.xml dosyanızdaki <provider> öğesinin android:authorities özelliğinde belirttiğiniz dize değeriyle eşleşmelidir.

Ayrıca, uygulamanın adı olan bir etiket ekleyin. Sistem arama ayarları, aranabilir uygulamaları numaralandırırken bu etiketi kullanır.

Özel bir öneri sağlamak amacıyla amaç işlemini tanımlamak için searchable.xml dosyasında "android.intent.action.VIEW" değerine sahip android:searchSuggestIntentAction değerinin yer alması gerekir. Bu, aşağıdaki bölümde açıklandığı üzere, bir arama terimi sağlamayla ilgili amaç işleminden farklıdır. Önerilerde amaç işlemini beyan etmenin diğer yolları için Niyet işlemini beyan etme bölümüne bakın.

Uygulamanız, intent işlemiyle birlikte android:searchSuggestIntentData özelliğiyle belirttiğiniz amaç verilerini de sağlamalıdır. Bu, URI'nın içeriğe işaret eden ilk bölümüdür ve URI'nın, bu içeriğe ait eşleme tablosundaki tüm satırlar için ortak olan kısmını açıklar. URI'nın her satır için benzersiz olan kısmı, Sütunları tanımlama bölümünde açıklandığı gibi SUGGEST_COLUMN_INTENT_DATA_ID alanıyla oluşturulur. Öneriler için amaç verilerini beyan etmenin diğer yolları için Amaç verilerini beyan etme bölümüne bakın.

android:searchSuggestSelection=" ?" özelliği, query() yönteminin selection parametresi olarak iletilen değeri belirtir. Soru işareti (?) değeri sorgu metniyle değiştirilir.

Son olarak, "true" değeriyle android:includeInGlobalSearch özelliğini de eklemeniz gerekir. Aşağıda örnek bir searchable.xml dosyası verilmiştir:

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/search_label"
    android:hint="@string/search_hint"
    android:searchSettingsDescription="@string/settings_description"
    android:searchSuggestAuthority="com.example.android.tvleanback"
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback"
    android:searchSuggestSelection=" ?"
    android:searchSuggestThreshold="1"
    android:includeInGlobalSearch="true">
</searchable>

Arama terimlerini işleme

Arama iletişim kutusunda, Sütunları tanımlama bölümünde açıklandığı gibi uygulamanızın sütunlarından birindeki değerle eşleşen bir kelime olduğunda sistem ACTION_SEARCH amacını tetikler. Uygulamanızda bu amacı işleyen etkinlik, değerlerinde belirli bir kelimeyi içeren sütunlar için depoda arama yapar ve bu sütunları içeren içerik öğelerinin listesini döndürür. AndroidManifest.xml dosyanızda, aşağıdaki örnekte gösterildiği gibi ACTION_SEARCH amacını işleyen etkinliği belirtirsiniz:

...
  <activity
      android:name="com.example.android.tvleanback.DetailsActivity"
      android:exported="true">

      <!-- Receives the search request. -->
      <intent-filter>
          <action android:name="android.intent.action.SEARCH" />
          <!-- No category needed, because the Intent will specify this class component -->
      </intent-filter>

      <!-- Points to searchable meta data. -->
      <meta-data android:name="android.app.searchable"
          android:resource="@xml/searchable" />
  </activity>
...
  <!-- Provides search suggestions for keywords against video meta data. -->
  <provider android:name="com.example.android.tvleanback.VideoContentProvider"
      android:authorities="com.example.android.tvleanback"
      android:exported="true" />
...

Etkinlik, searchable.xml dosyasına bir referansla aranabilir yapılandırmayı da açıklamalıdır. Genel arama iletişim kutusunu kullanmak için manifesto, hangi etkinliğin arama sorgularını alması gerektiğini açıklamalıdır. Manifest, <provider> öğesini de tam olarak searchable.xml dosyasında açıklandığı gibi açıklamalıdır.

Ayrıntılar ekranında uygulamanıza derin bağlantı oluşturun

Arama yapılandırmasını Arama önerilerini işleme bölümünde açıklandığı gibi ayarladıysanız ve SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_PRODUCTION_YEAR ve SUGGEST_COLUMN_DURATION alanlarını Sütun belirleme bölümünde açıklandığı gibi eşlediyseniz kullanıcı bir arama sonucu seçtiğinde açılan ayrıntılar ekranında içeriğinize ait bir izleme işlemine giden bir derin bağlantı görünür:

Ayrıntılar ekranında derin bağlantı

Kullanıcı, uygulamanızın ayrıntılar ekranında **Kullanılabilir** düğmesiyle tanımlanan bağlantısını seçtiğinde sistem, searchable.xml dosyasında "android.intent.action.VIEW" değeriyle android:searchSuggestIntentAction olarak ayarlanmış ACTION_VIEW öğesini işleyen etkinliği başlatır.

Etkinliğinizi başlatmak için özel amaç da oluşturabilirsiniz. Bu, Leanback örnek uygulamasında gösterilmektedir. Örnek uygulamanın, seçilen medyanın ayrıntılarını göstermek için kendi LeanbackDetailsFragment öğesini başlattığını unutmayın. Uygulamalarınızda, kullanıcıyı bir veya iki tıklama daha kaydetmek için medya içeriğini hemen oynatan etkinliği başlatın.

Arama davranışı

Arama, Android TV'de ana ekrandan ve uygulamanızın içinden kullanılabilir. Arama sonuçları bu iki durum için farklıdır.

Ana ekrandan arama yapın

Kullanıcı ana ekrandan arama yaptığında varlık kartında ilk sonuç görünür. İçeriği oynatabilen uygulamalar varsa kartın altında her birinin bağlantısı görünür:

TV Arama Sonucunu Oynatma

Varlık kartına programlı bir şekilde uygulama yerleştiremezsiniz. Oynatma seçeneği olarak dahil edilmesi için bir uygulamanın arama sonuçlarının, aranan içeriğin başlığı, yılı ve süresiyle eşleşmesi gerekir.

Kartın altında daha fazla arama sonucu bulabilirsiniz. Bunları görmek için kullanıcının uzaktan kumandaya basması ve aşağı kaydırması gerekir. Her uygulamaya ait sonuçlar ayrı bir satırda gösterilir. Satır sıralamasını kontrol edemezsiniz. İzleme işlemlerini destekleyen uygulamalar listede ilk sırada yer alır.

TV Arama Sonuçları

Uygulamanızdan arama

Kullanıcı, uzaktan kumandadan veya oyun kumandasından mikrofonu başlatarak uygulamanızın içinden de arama başlatabilir. Arama sonuçları, uygulama içeriğinin üzerinde tek bir satırda görüntülenir. Uygulamanız, arama sonuçlarını kendi genel arama sağlayıcısını kullanarak oluşturur.

TV Uygulama İçi Arama Sonuçları

Daha fazla bilgi

TV uygulamasında arama yapma hakkında daha fazla bilgi edinmek için Android arama özelliklerini uygulamanıza entegre etme ve Arama işlevi ekleme konularını okuyun.

SearchFragment ile uygulama içi arama deneyimini özelleştirme hakkında daha fazla bilgi için TV uygulamaları içinde arama bölümünü okuyun.