Criar um app de anotações

A criação de anotações é um recurso importante do Android, que melhora a produtividade do usuário em dispositivos de tela grande. Os apps de anotações permitem que os usuários escrevam e desenhem em uma janela flutuante ou em tela cheia, capturem e façam anotações no conteúdo da tela e salvem anotações para revisão posterior.

Os usuários podem acessar os apps de anotação na tela de bloqueio ou enquanto executam outros apps.

O suporte da stylus à criação de anotações proporciona uma experiência excepcional aos usuários.

Papel de anotações

O papel RoleManager.ROLE_NOTES identifica apps de anotações e concede a eles a permissão LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE.

Para adquirir o papel de anotações do seu app, faça o seguinte:

  1. Chame isRoleAvailable() para verificar o status do papel.
  2. Se o papel de anotações estiver disponível, chame createRequestRoleIntent() para receber uma intent específica.
  3. Chame startActivityForResult() com a intent de anotações para solicitar que o usuário conceda esse papel ao seu app.

Apenas um app pode ter esse papel.

O app é aberto em resposta a uma ação da intent implícita ACTION_CREATE_NOTE. Se invocado pela tela de bloqueio do dispositivo, o app é aberto em tela inteira. Se invocado enquanto a tela está desbloqueada, ele é aberto em uma janela flutuante.

Manifesto do app

Para se qualificar para o papel de anotações, o app precisa incluir a seguinte declaração no manifesto:

<activity
    android:name="YourActivityName"
    android:exported="true"
    android:showWhenLocked="true"
    android:turnScreenOn="true">
    <intent-filter>
        <action android:name="android.intent.action.CREATE_NOTE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

A declaração permite que os usuários atribuam o papel de anotações ao app, tornando-o o aplicativo de notas padrão:

  • ACTION_CREATE_NOTE define a ação da intent a que o app responde.

  • showWhenLocked torna o app acessível na tela de bloqueio do dispositivo.

  • turnScreenOn permite que o app ative a tela do dispositivo quando for executado.

Características do app

Um app de anotações diferenciado para telas grandes oferece um conjunto completo de recursos de anotação.

Suporte à stylus

Quando o app é invocado com o extra da intent EXTRA_USE_STYLUS_MODE definido como true, ele abre uma anotação que aceita entrada da stylus (ou toque do dedo).

Se o extra da intent estiver definido como false, seu app precisará abrir uma anotação que aceite entrada do teclado.

Acesso pela tela de bloqueio

O app precisa oferecer uma atividade em tela cheia que é executada quando ele é aberto na tela de bloqueio do dispositivo.

O app só vai mostrar notas históricas se o usuário tiver permitido (no estado do dispositivo desbloqueado) a exibição de notas anteriores. Caso contrário, quando aberto na tela de bloqueio, o app sempre precisará criar uma nova anotação.

É possível conferir se o app foi iniciado na tela de bloqueio com KeyguardManager#isKeyguardLocked(). Para pedir que o usuário autentique e desbloqueie o dispositivo, chame KeyguardManager#requestDismissKeyguard():

Kotlin

val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager

keyguardManager.requestDismissKeyguard(
    this,
    object : KeyguardDismissCallback() {

    override fun onDismissError() {
        // Unlock failed. Dismissing keyguard is not feasible.
    }

    override fun onDismissSucceeded() {
        // Unlock succeeded. Device is now unlocked.
    }

    override fun onDismissCancelled() {
        // Unlock failed. User cancelled operation or request otherwise cancelled.
    }
})

Java

KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);

boolean isLocked = keyguardManager.isKeyguardLocked();

keyguardManager.requestDismissKeyguard(
    this,
    new KeyguardManager.KeyguardDismissCallback() {

  @Override
  public void onDismissError() {
      // Unlock failed. Dismissing keyguard is not feasible.
  }

  @Override
  public void onDismissSucceeded() {
      // Unlock succeeded. Device is now unlocked.
  }

  @Override
  public void onDismissCancelled() {
      // Unlock failed. User cancelled operation or request otherwise cancelled.
  }
});

Janelas flutuantes

Para anotações contextuais, seu app precisa fornecer uma atividade que seja aberta em uma janela flutuante quando outro aplicativo estiver em execução.

O app precisa oferecer suporte ao modo multi-instance para que os usuários possam criar várias notas em diversas janelas flutuantes, mesmo quando o app de anotações for iniciado em tela cheia ou no modo de tela dividida.

Captura de conteúdo

A captura de conteúdo é um recurso importante dos apps de anotação. Com a captura de conteúdo, os usuários podem fazer capturas da tela por trás da janela flutuante do app de anotações. Os usuários podem capturar toda a tela ou parte dela, colar o conteúdo nas anotações e anotar ou destacar o conteúdo capturado.

Seu app de anotações precisa fornecer uma funcionalidade de interface que inicie uma ActivityResultLauncher criada por registerForActivityResult(). A ação da intent ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE é fornecida à tela de início diretamente ou por um ActivityResultContract.

Uma atividade do sistema captura o conteúdo, salva no dispositivo e retorna o URI de conteúdo para o app no argumento de callback de registerForActivityResult().

O exemplo a seguir usa um contrato StartActivityForResult genérico:

Kotlin

private val startForResult = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()) {
        result: ActivityResult ->
            if (result.resultCode == Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
                val uri = result.data?.data
                // Use the URI to paste the captured content into the note.
            }
    }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        NotesTheme {
            Surface(color = MaterialTheme.colorScheme.background) {
                CaptureButton(
                    onClick = {
                        Log.i("ContentCapture", "Launching intent...")
                        startForResult.launch(Intent(ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE))
                    })
            }
        }
    }
}

@Composable
fun CaptureButton(onClick: () -> Unit) {
    Button(onClick = onClick)
    {Text("Capture Content")}
}

Java

private final ActivityResultLauncher<Intent> startForResult = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
            Uri uri = result.getData() != null ? result.getData().getData() : null;
            // Use the URI to paste the captured content into the note.
        }
    });

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button captureButton = findViewById(R.id.capture_button);

    captureButton.setOnClickListener(
        view -> {
            Log.i("ContentCapture", "Launching intent...");
            startForResult.launch(new Intent(ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE));
        });
}

Seu app precisa processar todos os códigos de resultado:

Quando a captura de conteúdo for bem-sucedida, cole a imagem capturada na nota, por exemplo:

Kotlin

registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    result: ActivityResult ->
        if (result.resultCode == Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
            val uri = result.data?data
            // Use the URI to paste the captured content into the note.
        }
}

Java

registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
            Uri uri = result.getData() != null ? result.getData().getData() : null;
            // Use the URI to paste the captured content into the note.
        }
    });

O recurso de captura de conteúdo precisará ser exposto em uma funcionalidade de interface somente quando o app de anotações estiver em execução em uma janela flutuante, e não quando estiver em tela cheia, iniciado pela tela de bloqueio do dispositivo. Os usuários podem fazer capturas de tela do próprio app de anotações usando os recursos de captura de tela do dispositivo.

Para determinar se o app está em uma janela flutuante (ou um balão), chame estes métodos:

  • isLaunchedFromBubble() para verificar se o app de anotações não foi iniciado em tela cheia pela tela de bloqueio do dispositivo.
  • isRoleHeld(RoleManager.ROLE_NOTES) para verificar se o app é o padrão para anotações. O app pode ser executado em uma conversa ou outro tipo de balão caso não tenha o papel de anotações.

Outros recursos