TV uygulamalarını aranabilir hale getirme

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

Uygulamanız, kullanıcı arama iletişim kutusuna karakter girerken Android TV'nin önerilen arama sonuçlarını oluşturabileceği veri alanlarını Android TV'ye sağlamalıdır. Bunun için uygulamanızın, önerileri sunan bir İçerik Sağlayıcı'nın yanı sıra içerik sağlayıcıyı ve Android TV için diğer önemli bilgileri açıklayan bir searchable.xml yapılandırma dosyası uygulaması gerekir. Ayrıca, kullanıcı önerilen bir arama sonucunu seçtiğinde tetiklenen amacı işleyen bir etkinliğe ihtiyacınız vardır. Daha ayrıntılı bilgi için Özel arama önerileri ekleme bölümüne bakın. Bu kılavuz, Android TV uygulamalarıyla ilgili ana noktaları kapsar.

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

Bu kılavuzdaki örnek kod, Leanback örnek uygulamasından alınmıştır.

Sütunları tanımlama

SearchManager, beklediği veri alanlarını yerel bir veritabanının sütunları olarak temsil ederek tanımlar. Verilerinizin biçiminden bağımsız olarak, veri alanlarınızı genellikle içerik verilerinize erişen sınıfta yer alan bu sütunlarla 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 bölümüne bakın.

SearchManager sınıfı, Android TV için birkaç sütun içerir. Daha önemli olan 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 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 yapım yılı (zorunlu)
SUGGEST_COLUMN_DURATION Medyanızın milisaniye cinsinden süresi (gerekli)

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

İçeriğinize ilişkin 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çerik için ayrıntılar görünümünde uygulamanıza yönelik bir derin bağlantı ve diğer sağlayıcıların uygulamalarının bağlantılarını sağlar. Bu konu, Ayrıntılar ekranında uygulamanızın derin bağlantısı bölümünde daha ayrıntılı bir şekilde 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ızla harita oluştururken her satıra benzersiz bir kimlik vermek için _ID değerini 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;
  }
...

Önceki ö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. URI'nın ilk bölümü (tablodaki tüm satırlarda ortak olduğunda), 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 kısmı 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 tetiklenen amaç, SUGGEST_COLUMN_INTENT_DATA_ID ve android:searchSuggestIntentData özelliği ya da SUGGEST_COLUMN_INTENT_DATA alanı değerinin kombinasyonundan elde edilen amaç verilerini sağlar.

Arama önerisi verilerini sağlayın

Android TV arama iletişim kutusuna arama terimi önerileri döndürmek için bir İçerik Sağlayıcı uygulayın. Sistem, her harf yazıldığında query() yöntemini çağırarak öneri için içerik sağlayıcınızı 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);
}
...

İçerik sağlayıcı, manifest dosyanızda özel olarak muamele görür. 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 içerir. Ayrıca, Android genel aramasının bu aramadan 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 bir 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 de ekleyin. Sistem arama ayarları, aranabilir uygulamaları numaralandırırken bu etiketi kullanır.

searchable.xml dosyası, özel bir öneri sağlama intent işlemini tanımlamak için "android.intent.action.VIEW" değerine sahip android:searchSuggestIntentAction öğesini de içermelidir. Bu, aşağıdaki bölümde açıklandığı gibi, bir arama terimi sağlamaya yönelik amaç işleminden farklıdır. Öneriler için amaç işlemini bildirmenin diğer yolları için Amaç işlemini bildirme bölümüne bakın.

Uygulamanız, intent işlemiyle birlikte android:searchSuggestIntentData özelliğiyle belirttiğiniz amaç verilerini sağlamalıdır. Bu, URI'nın içeriği işaret eden ilk bölümüdür. Bu kısım, URI'nın söz konusu içeriğe ait eşleme tablosundaki tüm satırlarda 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ı ile oluşturulur. Öneriler için amaç verilerini bildirmenin diğer yollarını öğrenmek üzere Amaç verilerini bildirme 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. Örnek bir searchable.xml dosyası:

<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 olur olmaz sistem ACTION_SEARCH amacını tetikler. Uygulamanızda bu amacı işleyen etkinlik, depoda belirtilen kelimeyi değerlerinde içeren sütunları arar ve bu sütunlara sahip 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 referans vererek aranabilir yapılandırmayı da tanımlamalıdır. Genel arama iletişim kutusunu kullanmak için manifest, 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ığı şekilde ayarladıysanız ve SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_PRODUCTION_YEAR ve SUGGEST_COLUMN_DURATION alanlarını Sütun tanımlama bölümünde açıklandığı gibi eşlediyseniz ayrıntılar ekranında, kullanıcı bir arama sonucunu seçtiğinde açılan ayrıntılar ekranında içeriğinizle ilgili bir izleme işleminin derin bağlantısı görünür:

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

Kullanıcı, ayrıntılar ekranındaki **Kullanılabilir:** düğmesiyle tanımlanan bağlantıyı seçtiğinde sistem, searchable.xml dosyasında "android.intent.action.VIEW" değerine sahip android:searchSuggestIntentAction olarak ayarlanmış ACTION_VIEW işlemini gerçekleştiren etkinliği başlatır.

Etkinliğinizi başlatmak için özel bir amaç da oluşturabilirsiniz. Bu, Leanback örnek uygulamasında gösterilmiştir. Örnek uygulamanın, seçilen medyanın ayrıntılarını göstermek için kendi LeanbackDetailsFragment kampanyasını başlattığını unutmayın. Uygulamalarınızda, kullanıcının bir veya iki tıklama daha kazanabilmesi için medyayı 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 ilk sonuç bir varlık kartında görüntülenir. İçeriği oynatabilecek uygulamalar varsa kartın alt kısmında her birinin bağlantısı görünür:

TV Arama Sonucunu Oynatma

Bir uygulamayı varlık kartına programlı bir şekilde yerleştiremezsiniz. Oynatma seçeneğine dahil edilebilmesi için bir uygulamanın arama sonuçlarının, aranan içeriğin başlığı, yılı ve süresi ile eşleşmesi gerekir.

Kartın altında daha fazla arama sonucu gösterilebilir. 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örünür. Satır sırasını kontrol edemezsiniz. İzleme işlemlerini destekleyen uygulamalar listenin başındadır.

TV Arama Sonuçları

Uygulamanızdan arama yapın

Kullanıcı ayrıca uzaktan kumandadan veya oyun kumandası kumandasından mikrofonu başlatarak uygulamanızın içinden arama başlatabilir. Arama sonuçları, uygulama içeriğinin üstünde 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ında arama yapma bölümünü okuyun.