Criar um host de widgets de apps

A tela inicial do Android, disponível na maioria dos dispositivos com esse SO, permite que o usuário incorpore widgets de app para acesso rápido ao conteúdo. Se você estiver criando uma tela inicial substituta ou um app semelhante, também é possível permitir que o usuário incorpore widgets de app implementando um AppWidgetHost. Isso não é algo que a maioria dos apps precisará fazer, mas, se você estiver criando seu próprio host, é importante entender as obrigações contratuais com as quais um host concorda implicitamente.

Este documento se concentra nas responsabilidades envolvidas na implementação de um AppWidgetHost personalizado. Para ver um exemplo de como implementar um AppWidgetHost, consulte o código-fonte da tela inicial do Android.

Esta é uma visão geral dos principais conceitos e classes envolvidos na implementação de um AppWidgetHost personalizado:

  • Host de widgets de apps: o AppWidgetHost fornece a interação com o serviço do AppWidget para apps, como a tela inicial, que querem incorporar widgets de app à interface. O AppWidgetHost precisa ter um ID exclusivo no pacote do host. Esse ID continua persistente em todos os usos do host. Ele costuma ser um valor codificado que é atribuído ao seu aplicativo.
  • ID de widgets de apps: cada instância de widget de app tem um ID exclusivo atribuído no momento da vinculação. Consulte bindAppWidgetIdIfAllowed(), discutido mais detalhadamente em Vincular widgets de app. O ID exclusivo é recebido pelo host por meio de allocateAppWidgetId(). Ele é persistente durante o ciclo de vida do widget, isto é, até ser excluído pelo host. Todo estado específico do host, como o tamanho e local do widget, precisa ser mantido pelo pacote de hospedagem e associado ao ID do widget de app.
  • Visualização do host de widgets de apps: o AppWidgetHostView pode ser utilizado como um frame em que o widget esteja agrupado sempre que precisar ser exibido. Um widget de app é atribuído a um AppWidgetHostView sempre que o widget for inflado pelo host.
  • Pacote de opções: o AppWidgetHost usa o pacote de opções para comunicar informações ao AppWidgetProvider sobre como o widget está sendo exibido, por exemplo, a faixa de tamanho e se o widget está em uma tela de bloqueio ou na tela inicial. Essas informações permitem que o AppWidgetProvider personalize o conteúdo e a aparência do widget com base em como e onde ele é exibido. Usa-se updateAppWidgetOptions() e updateAppWidgetSize() para modificar o pacote de um widget de app. Esses dois métodos acionam um callback para o AppWidgetProvider.

Vincular widgets de app

Quando um usuário adiciona um widget de app a um host, um processo chamado vinculação ocorre. Vinculação refere-se à associação de um determinado ID de widget de app a um host e a um AppWidgetProvider específicos. Há maneiras diferentes de fazer isso, dependendo da versão do Android em que seu app está sendo executado.

Vincular widgets de app no Android 4.0 e versões anteriores

Em dispositivos com o Android 4.0 ou versões anteriores, os usuários adicionam widgets de app por meio de uma atividade do sistema que permite escolher um widget. Isso causa, implicitamente, uma verificação de permissão, ou seja, ao adicionar o widget de app, o usuário está implicitamente concedendo ao seu aplicativo permissão para adicionar widgets de apps ao host. Aqui está um exemplo que ilustra essa abordagem, retirado da tela de início original. Neste snippet, um manipulador de eventos invoca startActivityForResult() com o código de solicitação REQUEST_PICK_APPWIDGET em resposta a uma ação do usuário:

Kotlin

val REQUEST_CREATE_APPWIDGET = 5
val REQUEST_PICK_APPWIDGET = 9
...
override fun onClick(dialog: DialogInterface?, which: Int) {
    when (which) {
        ...
        AddAdapter.ITEM_APPWIDGET -> {
            ...
            val appWidgetId: Int = appWidgetHost.allocateAppWidgetId()
            val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            }
            ...
            startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET)
        }
        ...
    }
}

Java

private static final int REQUEST_CREATE_APPWIDGET = 5;
private static final int REQUEST_PICK_APPWIDGET = 9;
...
public void onClick(DialogInterface dialog, int which) {
    switch (which) {
    ...
        case AddAdapter.ITEM_APPWIDGET: {
            ...
            int appWidgetId =
                    Launcher.this.appWidgetHost.allocateAppWidgetId();
            Intent pickIntent =
                    new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
            pickIntent.putExtra
                    (AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            ...
            startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
            break;
    }
    ...
}

Quando a atividade do sistema termina, ele retorna um resultado com o widget de app escolhido pelo usuário para sua atividade. No exemplo a seguir, a atividade responde chamando addAppWidget() para adicionar o widget do app:

Kotlin

class Launcher : Activity(), View.OnClickListener, View.OnLongClickListener {
    ...
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        waitingFroResult = false

        if (resultCode == RESULT_OK && addItemCellInfo != null) {
            when (requestCode) {
                ...
                REQUEST_PICK_APPWIDGET -> addAppWidget(data)
                REQUEST_CREATE_APPWIDGET ->
                    completeAddAppWidget(data, addItemCellInfo, !desktopLocked)
                ...
            }
        }
        ...
    }
}

Java

public final class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener {
    ...
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        waitingForResult = false;

        if (resultCode == RESULT_OK && addItemCellInfo != null) {
            switch (requestCode) {
                ...
                case REQUEST_PICK_APPWIDGET:
                    addAppWidget(data);
                    break;
                case REQUEST_CREATE_APPWIDGET:
                    completeAddAppWidget(data, addItemCellInfo, !desktopLocked);
                    break;
                }
        }
        ...
    }
}

O método addAppWidget() verifica se o widget do app precisa ser configurado antes de ser adicionado:

Kotlin

fun addAppWidget(data: Intent?) {
    if (data != null) {
        val appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)

        val customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET)
        val appWidget: AppWidgetProviderInfo? = appWidgetManager.getAppWidgetInfo(appWidgetId)

        appWidget?.configure?.apply {
            // Launch over to configure widget, if needed.
            val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE)
            intent.component = this
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            startActivityForResult(intent, REQUEST_CREATE_APPWIDGET)
        } ?: run {
            // Otherwise, finish adding the widget.
        }
    }
}

Java

void addAppWidget(Intent data) {
    int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

    String customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET);
    AppWidgetProviderInfo appWidget =
            appWidgetManager.getAppWidgetInfo(appWidgetId);

    if (appWidget.configure != null) {
        // Launch over to configure widget, if needed.
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
        intent.setComponent(appWidget.configure);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
    } else {
        // Otherwise, finish adding the widget.
    }
}

Para mais informações sobre a configuração, consulte Criar uma atividade para configurar widgets de app.

Quando o widget do app estiver pronto, o próximo passo será fazer a tarefa de adicioná-lo ao espaço de trabalho. A tela de início original usa um método chamado completeAddAppWidget() para fazer isso.

Vincular widgets de apps no Android 4.1 e versões mais recentes

O Android 4.1 adiciona APIs para um processo de vinculação mais simples. Essas APIs também possibilitam que um host forneça uma IU personalizada para vinculação. Para usar esse processo aprimorado, seu app precisa declarar a permissão BIND_APPWIDGET no manifesto:

<uses-permission android:name="android.permission.BIND_APPWIDGET" />

No entanto, esse é apenas o primeiro passo. No momento da execução é necessário que o usuário conceda explicitamente a permissão para seu app, permitindo que ele adicione widgets de apps ao host. Para testar se seu app tem permissão para adicionar o widget, use o método bindAppWidgetIdIfAllowed(). Se bindAppWidgetIdIfAllowed() retornar false, é necessário que seu app exiba uma caixa de diálogo solicitando que o usuário conceda a permissão, seja ela "permitir" ou "sempre permitir", para abranger todas as futuras adições de widgets. Este snippet demonstra um exemplo de como exibir a caixa de diálogo:

Kotlin

val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName)
    // This is the options bundle discussed above
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options)
}
startActivityForResult(intent, REQUEST_BIND_APPWIDGET)

Java

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
// This is the options bundle discussed above
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
startActivityForResult(intent, REQUEST_BIND_APPWIDGET);

Também é necessário que o host verifique se o usuário adicionou ou não um widget de app que precisa de configuração. Para ver mais discussões neste tópico, consulte Criar uma atividade para configurar widgets de app.

Responsabilidades do host

Os desenvolvedores de widgets podem especificar diversas configurações para eles usando os metadados AppWidgetProviderInfo. Essas opções de configuração, discutidas mais detalhadamente abaixo, podem ser recuperadas pelo host a partir do objeto AppWidgetProviderInfo associado a um provedor de widgets.

Independentemente da versão do Android que você está segmentando, todos os hosts têm as seguintes responsabilidades:

  • Ao adicionar um widget, é necessário alocar o ID do widget conforme descrito acima. Também é necessário garantir que, quando um widget for removido do host, você chame deleteAppWidgetId() para desalocar o ID do widget.
  • Ao adicionar um widget, lembre-se de lançar a atividade de configuração, se ela existir, conforme descrito em Atualizar o widget de app a partir da atividade de configuração. Essa é uma etapa necessária para vários widgets de app antes que eles possam ser exibidos corretamente.
  • Cada widget de app especifica uma largura e altura mínimas em dps, conforme definido nos metadados AppWidgetProviderInfo (usando android:minWidth e android:minHeight). Verifique se o widget está disposto com pelo menos essa quantidade de dps. Por exemplo, muitos hosts alinham ícones e widgets em uma grade. Nesse cenário, por padrão, é necessário que o host adicione o widget de app usando o número mínimo de células que satisfaça as restrições de minWidth e minHeight.

Além dos requisitos listados acima, versões específicas da plataforma introduzem recursos que colocam novas responsabilidades no host.

Para qual versão você está direcionando?

A abordagem que é usada para implementar seu host depende da versão do Android que você está direcionando. Muitos dos recursos descritos nesta seção foram introduzidos na versão 3.0 ou mais recentes. Exemplo:

  • O Android 3.0 (API de nível 11) introduz o comportamento de avanço automático para widgets.
  • O Android 3.1 (API de nível 12) introduz a possibilidade de redimensionar widgets.
  • O Android 4.0 (API de nível 15) introduz uma mudança na política de padding que passa para o host a responsabilidade de gerenciar o padding.
  • O Android 4.1 (API de nível 16) adiciona uma API que permite ao provedor de widgets receber informações mais detalhadas sobre o ambiente em que as instâncias de widget estão sendo hospedadas.
  • O Android 4.2 (API de nível 17) introduz o pacote de opções e o método bindAppWidgetIdIfAllowed(). Ele também introduz os widgets da tela de bloqueio.

Se você está direcionando para dispositivos mais antigos, referencie a tela de início original como exemplo.

As seções a seguir fornecem mais detalhes sobre recursos que colocam novas responsabilidades no host.

Android 3.0

O Android 3.0 (API de nível 11) introduz a possibilidade de um widget especificar autoAdvanceViewId(). É necessário que esse ID de visualização aponte para uma instância de um Advanceable, como StackView ou AdapterViewFlipper. Isso indica que o host precisa chamar advance() nessa visualização em um intervalo julgado como apropriado por ele, considerando se é ou não vantajoso avançar o widget. Por exemplo, o host provavelmente não faria isso se estivesse em outra página ou se a tela estivesse desativada.

Android 3.1

O Android 3.1 (API de nível 12) introduz a possibilidade de redimensionar widgets. Um widget pode especificar que é redimensionável usando o atributo android:resizeMode nos metadados AppWidgetProviderInfo e indicar se é ou não compatível com redimensionamento horizontal e/ou vertical. A partir do Android 4.0 (API de nível 14), o widget também pode especificar um android:minResizeWidth e/ou android:minResizeHeight.

É responsabilidade do host permitir que o widget seja redimensionado horizontal e/ou verticalmente, conforme especificado pelo widget. Um widget que especifica que é redimensionável pode ser aumentado arbitrariamente, mas não pode ser menor do que os valores especificados por android:minResizeWidth e android:minResizeHeight. Para ver uma amostra de implementação, consulte AppWidgetResizeFrame em Launcher2.

No Android 4.0

O Android 4.0 (API de nível 15) introduz uma mudança na política de padding que passa para o host a responsabilidade de gerenciar o padding. A partir da versão 4.0, os widgets de apps não incluem mais os próprios paddings. Em vez disso, o sistema adiciona padding para cada widget, com base nas características da tela atual. Isso leva a uma apresentação uniforme e consistente de widgets em uma grade. Para auxiliar os aplicativos que hospedam widgets de apps, a plataforma fornece o método getDefaultPaddingForWidget(). Os aplicativos podem chamar esse método para receber o padding definido pelo sistema e contabilizá-lo ao calcular o número de células a serem alocadas para o widget.

Android 4.1

O Android 4.1 (API de nível 16) adiciona uma API que permite ao provedor de widgets receber informações mais detalhadas sobre o ambiente em que as instâncias de widget estão sendo hospedadas. Especificamente, o host disponibiliza dicas para o provedor de widgets sobre o tamanho em que o widget está sendo exibido. É responsabilidade do host fornecer essas informações de tamanho.

O host fornece essas informações por meio de updateAppWidgetSize(). O tamanho é especificado como largura e altura mínima e máxima em dps. A razão pela qual um intervalo é especificado (em oposição a um tamanho fixo) é porque a largura e a altura de um widget podem mudar com a orientação. Não é aconselhável que o host precise atualizar todos os widgets na rotação, porque isso pode causar uma lentidão grave no sistema. É necessário que esses valores sejam atualizados assim que o widget for colocado, sempre que ele for redimensionado e sempre que a tela de início inflar o widget pela primeira vez em uma determinada inicialização, já que os valores não são mantidos na inicialização.

Android 4.2

O Android 4.2 (API de nível 17) adiciona a possibilidade de o pacote de opções ser especificado no momento da vinculação. Essa é a maneira ideal de especificar opções de widget de apps, incluindo o tamanho, já que concede ao AppWidgetProvider acesso imediato aos dados de opções na primeira atualização. Isso pode ser feito usando o método bindAppWidgetIdIfAllowed(). Para ver mais discussões sobre esse tópico, consulte Vincular widgets de app.

O Android 4.2 também introduz widgets de tela de bloqueio. Ao hospedar widgets na tela de bloqueio, o host precisa especificar essas informações no pacote de opções do widget de app. O AppWidgetProvider pode usar essas informações para definir adequadamente o estilo do widget. Para designar um widget como bloqueio de tela, use updateAppWidgetOptions() e inclua o campo OPTION_APPWIDGET_HOST_CATEGORY com o valor WIDGET_CATEGORY_KEYGUARD. Por padrão, essa opção é WIDGET_CATEGORY_HOME_SCREEN, então, não é explicitamente necessário definir isso para um host da tela inicial.

O host só pode adicionar widgets de app apropriados para seu aplicativo. Por exemplo, se ele for uma tela inicial, verifique se o atributo android:widgetCategory nos metadados AppWidgetProviderInfo inclui a sinalização WIDGET_CATEGORY_HOME_SCREEN. Da mesma forma, para a tela de bloqueio, verifique se o campo inclui a sinalização WIDGET_CATEGORY_KEYGUARD. Para ver mais discussões sobre esse tema, consulte Ativar widgets de app na tela de bloqueio.