# Как сделать ИИ-оверлей на Android с распознаванием экрана: плавающая кнопка, скриншоты и OCR

> AI Release · @ai_release1 · https://ai-release.org/guides/kak-sdelat-ii-overley-na-android-s-raspoznavaniem-ekran.html

## TL;DR
- Время выполнения: 90–120 минут
- Уровень сложности: Advanced
- Понадобится: Android Studio (Kotlin), устройство или эмулятор с Android 10 (API 29) и выше, ADB в `PATH`, включённая «Отладка по USB», библиотека OCR на выбор (ML Kit Text Recognition или Tesseract — актуальную версию и координаты зависимости сверьте с документацией выбранной библиотеки)

Итог: плавающая кнопка поверх любых приложений, по нажатию — снимок экрана, OCR и вывод распознанного текста в лог и в собственный оверлей.

## Требования и подготовка
![Иллюстрация к разделу «Требования и подготовка»](https://ai-release.org/guides/img/kak-sdelat-ii-overley-na-android-s-raspoznavaniem-ekran-ru-1.jpg)


- ОС для сборки: Windows/macOS/Linux с установленной Android Studio и JDK из её комплекта.
- Устройство с API 29+ (с Android 10 MediaProjection требует foreground service с типом `mediaProjection`).
- ADB, проверенный командой `adb devices` — в списке должно быть ваше устройство в статусе `device`.
- Пакет приложения задайте сразу, например `com.example.screenocr` — он понадобится в командах `appops` и `logcat`.
- Свободное место: под SDK-платформу и зависимости OCR-библиотеки (у ML Kit модель распознавания догружается после установки — размер сверьте с документацией).

Чек-лист перед стартом:

- [ ] `adb devices` показывает устройство
- [ ] В настройках устройства разрешена установка приложений из Android Studio
- [ ] В проекте создан пустой Activity (Kotlin)
- [ ] Добавлена зависимость OCR-библиотеки и проект синхронизирован (Gradle sync)
- [ ] Вы знаете имя пакета приложения

**Пример:** если `adb devices` печатает пустой список, включите «Отладка по USB» в «Настройки → О системе → Номер сборки (7 тапов) → Для разработчиков». Если эмулятора нет, поднимите его по инструкции из гайда [Как запускать Android-приложения и игры на ПК: эмуляторы, Waydroid и облачные сервисы](https://ai-release.org/guides/kak-zapuskat-android-prilozheniya-i-igry-na-pk-emulyato.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

Схема пайплайна, которую будем собирать:

1. Плавающая кнопка (`TYPE_APPLICATION_OVERLAY`) — ожидаемый результат: кружок висит поверх рабочего стола и других приложений.
2. Запрос захвата экрана (`MediaProjectionManager.createScreenCaptureIntent()`) — ожидаемый результат: системный диалог «Начать запись экрана?».
3. `VirtualDisplay` + `ImageReader` — ожидаемый результат: в лог приходит размер кадра.
4. OCR по `Bitmap` — ожидаемый результат: в лог приходит распознанная строка.

## Шаг 1. Каркас приложения и плавающая кнопка

**Что сделать:** объявите разрешения и foreground service в `app/src/main/AndroidManifest.xml`, затем добавьте кнопку через `WindowManager`.

**Зачем:** окно `TYPE_APPLICATION_OVERLAY` — единственный штатный способ рисовать поверх чужих приложений без root. Разрешение `SYSTEM_ALERT_WINDOW` выдаётся пользователем вручную и без него `addView` падает.

**Как проверить:** после запуска приложения кнопка видна поверх рабочего стола, а `appops` показывает `allow`.

```xml
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<service
    android:name=".CaptureService"
    android:exported="false"
    android:foregroundServiceType="mediaProjection"/>
```

```kotlin
private fun showFloatingButton() {
    if (!Settings.canDrawOverlays(this)) {
        startActivity(
            Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:$packageName"))
        )
        return
    }
    val button = Button(this).apply { text = "OCR" }
    val params = WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        PixelFormat.TRANSLUCENT
    ).apply {
        gravity = Gravity.TOP or Gravity.START
        x = 24
        y = 300
    }
    getSystemService(WINDOW_SERVICE).let {
        (it as WindowManager).addView(button, params)
    }
}
```

**Пример:** `adb shell appops get com.example.screenocr SYSTEM_ALERT_WINDOW` → `SYSTEM_ALERT_WINDOW: allow`. Если вывод `SYSTEM_ALERT_WINDOW: deny` — разрешение не выдано, кнопки на экране не будет.

Почему это работает: `WindowManager` рисует view в отдельном окне, которое принадлежит вашему процессу, но отображается в общем слое поверх остальных приложений.

## Шаг 2. Захват экрана через MediaProjection и OCR
![Иллюстрация к разделу «Шаг 2. Захват экрана через MediaProjection и OCR»](https://ai-release.org/guides/img/kak-sdelat-ii-overley-na-android-s-raspoznavaniem-ekran-ru-2.jpg)


**Что сделать:** по нажатию кнопки запросите согласие на захват, передайте `resultCode` и `Intent` в foreground service, создайте `VirtualDisplay` с `ImageReader` и отдайте полученный `Bitmap` в OCR.

**Зачем:** MediaProjection отдаёт видеопоток экрана, а `ImageReader` превращает один кадр в `Bitmap` — это и есть «скриншот» без хранения файла на диске.

**Как проверить:** в `adb logcat -s CAPTURE:D OCR:D` появляются размер кадра и распознанный текст.

| Компонент | Роль | Как проверить |
|---|---|---|
| `WindowManager` + `TYPE_APPLICATION_OVERLAY` | плавающая кнопка | `adb shell appops get <пакет> SYSTEM_ALERT_WINDOW` |
| `MediaProjectionManager` | запрос доступа к экрану | системный диалог захвата |
| `ImageReader` + `VirtualDisplay` | кадры экрана | лог с тегом `CAPTURE` |
| OCR-библиотека | текст из `Bitmap` | лог с тегом `OCR` |

```kotlin
// В Activity: получаем согласие пользователя
val mgr = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(mgr.createScreenCaptureIntent(), REQ_CAPTURE)

// В CaptureService: стартуем foreground и поднимаем захват
startForeground(1, buildNotification(),
    ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)

val projection = mgr.getMediaProjection(resultCode, data)
val dm = resources.displayMetrics
val reader = ImageReader.newInstance(
    dm.widthPixels, dm.heightPixels, PixelFormat.RGBA_8888, 2)

projection.createVirtualDisplay(
    "ocr-capture",
    dm.widthPixels, dm.heightPixels, dm.densityDpi,
    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
    reader.surface, null, null)

reader.setOnImageAvailableListener({ r ->
    val image = r.acquireLatestImage() ?: return@setOnImageAvailableListener
    val bitmap = image.toBitmap()
    image.close()
    runOcr(bitmap)
}, handler)
```

Конвертация кадра с учётом `rowStride` (иначе картинка «съезжает» по диагонали):

```kotlin
fun Image.toBitmap(): Bitmap {
    val plane = planes[0]
    val rowPadding = plane.rowStride - plane.pixelStride * width
    val raw = Bitmap.createBitmap(
        width + rowPadding / plane.pixelStride, height, Bitmap.Config.ARGB_8888)
    raw.copyPixelsFromBuffer(plane.buffer)
    return Bitmap.createBitmap(raw, 0, 0, width, height)
}
```

```kotlin
private fun runOcr(bitmap: Bitmap) {
    val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
    recognizer.process(InputImage.fromBitmap(bitmap, 0))
        .addOnSuccessListener { Log.d("OCR", it.text) }
        .addOnFailureListener { Log.e("OCR", "fail", it) }
}
```

**Пример:** на экране открыт текст «Оплата прошла успешно». В логе: `D/OCR: Оплата прошла успешно`. Плавающую кнопку перед снимком скройте (`button.visibility = View.GONE`) или исключите из кадра — иначе она попадёт в распознанный текст.

Почему это работает: `VirtualDisplay` дублирует содержимое дисплея в `Surface`, который принадлежит `ImageReader`; библиотека OCR принимает `Bitmap` и возвращает блок текста.

## Шаг 3. Проверка работы и финальный результат
![Иллюстрация к разделу «Шаг 3. Проверка работы и финальный результат»](https://ai-release.org/guides/img/kak-sdelat-ii-overley-na-android-s-raspoznavaniem-ekran-ru-3.jpg)


**Что сделать:** установите сборку, выдайте разрешения, нажмите кнопку и снимите лог.

**Зачем:** так вы отделяете проблему разрешений от проблемы OCR — по тегам видно, на каком шаге пайплайн остановился.

**Как проверить:** в логе есть и `CAPTURE`, и `OCR`; на экране — распознанный текст в вашем оверлее.

```bash
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb logcat -c
adb logcat -s CAPTURE:D OCR:D
```

Ожидаемый вывод:

```
D/CAPTURE: frame 1080x2400
D/OCR: Оплата прошла успешно
```

Финальный чек-лист успеха:

- [ ] Кнопка видна поверх другого приложения (например, поверх браузера)
- [ ] При первом нажатии появляется системный диалог захвата экрана
- [ ] В `dumpsys` есть запущенный сервис: `adb shell dumpsys activity services com.example.screenocr | grep -i mediaprojection`
- [ ] В логе есть строка с тегом `OCR` и непустым текстом
- [ ] Повторное нажатие не создаёт второй `VirtualDisplay`

Дальше распознанный текст можно отправить в модель — локальный запуск описан в гайде [Как запустить ИИ-модель локально на Android: приложения и настройка](https://ai-release.org/guides/kak-zapustit-ii-model-lokalno-na-android-prilozheniya-i.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide), а если нужен Linux-инструментарий рядом — в [Как запустить Linux на Android без root: Termux, PRoot и ИИ-инструменты](https://ai-release.org/guides/kak-zapustit-linux-na-android-bez-root-termux-proot-i-i.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

## Возможные проблемы и решения

**Проблема. Кнопка не появляется, приложение падает при запуске**
- Ошибка: `android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@8f3a1c -- permission denied for window type 2038`
- Причина: не выдано разрешение `SYSTEM_ALERT_WINDOW` (тип окна 2038 — это `TYPE_APPLICATION_OVERLAY`); хеш объекта в тексте у вас будет другим.
- Решение: откройте «Настройки → Приложения → <ваше приложение> → Поверх других приложений → Разрешить», либо выполните `adb shell appops set com.example.screenocr SYSTEM_ALERT_WINDOW allow` и перезапустите приложение. Проверка: `adb shell appops get com.example.screenocr SYSTEM_ALERT_WINDOW` → `allow`.

**Проблема. Захват экрана падает сразу после согласия пользователя**
- Ошибка: `java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION`
- Причина: `getMediaProjection()` вызван не из foreground service или сервис объявлен без `android:foregroundServiceType="mediaProjection"`.
- Решение: перенесите вызов в `CaptureService`, добавьте атрибут в манифест и вызывайте `startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)` до создания проекции. Проверка: `adb shell dumpsys activity services com.example.screenocr | grep -i mediaprojection` показывает ваш сервис.

**Проблема. Кадр получен, но текст пустой**
- Ошибка: `MlKitException: Waiting for the text recognition model to be downloaded. Please wait.` (префикс класса зависит от версии библиотеки — сверьтесь с документацией)
- Причина: модель OCR ещё не догружена на устройство, либо снимок сделан в приложении с `FLAG_SECURE` — там система отдаёт чёрный кадр.
- Решение: дайте устройству доступ в сеть и повторите попытку; для проверки снимите экран с обычным текстом (заметки, браузер). Если нужен офлайн-режим — используйте OCR-движок со встроенной моделью, состав и версию сверьте с документацией.

## Резюме и следующие шаги
Вы собрали оверлей из трёх частей: плавающая кнопка на `TYPE_APPLICATION_OVERLAY`, захват кадра через `MediaProjection` + `ImageReader` и OCR по `Bitmap` с выводом в лог. Следующий шаг — отправить распознанный текст в модель и вернуть ответ в то же плавающее окно, не забывая скрывать кнопку перед снимком.

## FAQ

**Нужен ли root для ИИ-оверлея с распознаванием экрана?**
Нет. `SYSTEM_ALERT_WINDOW` и `MediaProjection` — штатные механизмы Android, пользователь подтверждает их вручную. Root не требуется ни для кнопки, ни для снимка экрана.

**Почему OCR возвращает пустой текст на некоторых приложениях?**
Скорее всего, приложение ставит `FLAG_SECURE` (защита от скриншотов) — система отдаёт чёрный кадр. Проверьте на обычном приложении с текстом: если там текст распознаётся, дело именно в защите окна.

**Можно ли обойтись без MediaProjection и читать экран через AccessibilityService?**
Можно как альтернативу, но это другой источник данных и другие ограничения по доступным элементам интерфейса; конкретный набор возможностей сверьте с документацией по AccessibilityService для вашей версии Android.

**Куда девать распознанный текст дальше?**
В лог — для отладки, в собственный оверлей — для пользователя, в модель — для ответа по содержимому экрана. Для локального запуска модели на телефоне см. гайд [Как запустить ИИ-модель локально на Android: приложения и настройка](https://ai-release.org/guides/kak-zapustit-ii-model-lokalno-na-android-prilozheniya-i.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).
