Команды для поставщиков контента (content)

Рейтинг: 67.6% · 8 голосов
Полный курс по Android Debug Bridge: установка, подключение, shell, логи, dumpsys, автоматизация, root, беспроводная отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
android_roman
Сообщения: 45
Зарегистрирован: 11 май 2026, 05:31

Команды для поставщиков контента (content)

Сообщение android_roman »

АкадемияADB: Android Debug BridgeГлава 17 из 29
Оглавление курса (29)
  1. Введение в Android Debug Bridge
  2. Установка и настройка рабочей среды
  3. Подключение устройства (проводное и беспроводное)
  4. Базовые команды ADB и управление сервером
  5. Команды состояния и перезагрузки
  6. Навигация по файловой системе
  7. Управление пакетами приложений
  8. Логирование с помощью logcat
  9. Системные дампы и диагностика (dumpsys)
  10. Анализ производительности в реальном времени
  11. Эмуляция ввода (input)
  12. Управление Activity и Intent (am)
  13. Работа с оконным менеджером (wm)
  14. Захват экрана и запись видео
  15. Root-доступ и его применение
  16. Модификация системных настроек через settings
  17. Команды для поставщиков контента (content) (вы здесь)
  18. Резервное копирование и восстановление (backup)
  19. Проброс портов и туннелирование
  20. Беспроводная отладка (Wi-Fi)
  21. Взаимодействие с эмуляторами
  22. Написание скриптов на Bash/CMD/PowerShell
  23. ADB в языках программирования
  24. Автоматизация тестирования с ADB
  25. Безопасность и лучшие практики
  26. ADB на Android TV, Wear OS и IoT
  27. Восстановление и низкоуровневые операции
  28. Расширенные возможности оболочки и инструменты
  29. Отладка самого ADB и устранение неполадок
Поставщик контента (ContentProvider) отвечает за доступ к структурированным данным Android: контакты, журнал звонков, SMS, календарь, медиатека, системные настройки. Каждый провайдер слушает запросы по URI вида content://authority/path и поддерживает четыре операции: query, insert, update, delete. Команда content в adb shell работает как консольный клиент к этому механизму. По сути вы получаете SQL поверх системных данных, не написав ни строчки Java. В главе 16 мы правили настройки утилитой settings, под капотом там провайдер content://settings, сейчас увидим его без обертки.

Сразу про права. Команда content выполняется от пользователя shell (uid 2000), его разрешения зашиты в системный пакет com.android.shell и от прошивки к прошивке почти не меняются. Контакты, журнал звонков, календарь и медиатека на стоковом Android обычно доступны без root. SMS закрыты: у shell нет READ_SMS, понадобится root, об этом ниже.

Общий синтаксис:

Код: Выделить всё

content query   --uri <URI> [--user <ID>] [--projection <столбцы>] [--where <условие>] [--sort <порядок>]
content insert  --uri <URI> --bind <столбец:тип:значение> [--bind ...]
content update  --uri <URI> [--where <условие>] --bind <столбец:тип:значение>
content delete  --uri <URI> [--where <условие>]
content call    --uri <URI> --method <метод> [--arg <аргумент>]
content read    --uri <URI>
content gettype --uri <URI>
Типы значений в --bind: b (boolean), s (string), i (integer), l (long), f (float), d (double), на свежих версиях еще n (null). Проекция задается именами столбцов через двоеточие. Условие where пишется обычным SQL, как после слова WHERE.

content query, URI, проекции, условия:

Тренируемся на провайдере настроек, он открыт для shell:

Код: Выделить всё

adb shell content query --uri content://settings/system --projection name:value --where "name='screen_brightness'"
Типичный вывод:

Код: Выделить всё

Row: 0 name=screen_brightness, value=96
Каждая строка результата печатается как Row: N столбец=значение. Без --projection вернутся все столбцы (на больших таблицах это простыня), без --where все строки. Пустой результат выглядит как No result found., это не ошибка. Сортировка как в SQL:

Код: Выделить всё

adb shell content query --uri content://settings/system --projection name:value --sort "name ASC"
Какие authority вообще есть на устройстве, подскажет менеджер пакетов (dumpsys мы разбирали в главе 9):

Код: Выделить всё

adb shell dumpsys package providers | grep -iE "contacts|calendar|media|telephony"
insert, update, delete:

Код: Выделить всё

adb shell content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:1
adb shell content update --uri content://settings/system --bind value:i:0 --where "name='user_rotation'"
adb shell content delete --uri content://settings/system --where "name='my_test_flag'"
Первая команда поворачивает экран в ландшафт (user_rotation 1 означает 90 градусов), вторая возвращает портрет, третья удаляет строку. При успехе все три молчат, проверяйте повторным query. У провайдера настроек insert работает как upsert: если name уже существует, значение обновится. В реальной работе настройки удобнее крутить через settings put из главы 16, здесь они нужны для демонстрации механики bind: имя столбца, тип, значение через двоеточие.

Контакты:

Контакты живут за authority com.android.contacts. Список с именами:

Код: Выделить всё

adb shell content query --uri content://com.android.contacts/contacts --projection _id:display_name --sort "display_name ASC"
Row: 0 _id=4, display_name=Avito kurier
Row: 1 _id=2, display_name=Mama
Row: 2 _id=7, display_name=Ofis SPb
Телефоны лежат в таблице data, для них есть удобный путь data/phones, сам номер в столбце data1:

Код: Выделить всё

adb shell content query --uri content://com.android.contacts/data/phones --projection display_name:data1
Создание контакта выполняется в два шага, так устроена схема ContactsContract. Сначала запись в raw_contacts, потом строки данных с mimetype. Узнать _id свежесозданной записи проще всего сортировкой по убыванию:

Код: Выделить всё

adb shell content insert --uri content://com.android.contacts/raw_contacts --bind account_type:s:adb.local --bind account_name:s:adb
adb shell content query --uri content://com.android.contacts/raw_contacts --projection _id --sort "_id DESC"
Row: 0 _id=15
Теперь имя и мобильный номер (data2 со значением 2 означает тип "мобильный"):

Код: Выделить всё

adb shell content insert --uri content://com.android.contacts/data --bind raw_contact_id:i:15 --bind mimetype:s:vnd.android.cursor.item/name --bind data1:s:IvanTestov
adb shell content insert --uri content://com.android.contacts/data --bind raw_contact_id:i:15 --bind mimetype:s:vnd.android.cursor.item/phone_v2 --bind data1:s:+79161234567 --bind data2:i:2
Контакт появится в звонилке только после добавления строк data, голая запись raw_contacts в списке не видна. Если значение с пробелом, оборачивайте весь удаленный вызов в одинарные кавычки, а bind в двойные, иначе пробел разорвет аргумент:

Код: Выделить всё

adb shell 'content insert --uri content://com.android.contacts/data --bind raw_contact_id:i:15 --bind mimetype:s:vnd.android.cursor.item/name --bind "data1:s:Ivan Testov"'
Удаление через провайдер по умолчанию мягкое, контакт лишь помечается deleted=1 до ближайшей синхронизации. Жесткое удаление выполняется с параметром caller_is_syncadapter:

Код: Выделить всё

adb shell 'content delete --uri "content://com.android.contacts/raw_contacts?caller_is_syncadapter=true" --where "_id=15"'
Журнал звонков:

Код: Выделить всё

adb shell content query --uri content://call_log/calls --projection number:type:date:duration --sort "date DESC"
Row: 0 number=+74951234567, type=3, date=1781239512345, duration=0
Row: 1 number=+79261112233, type=2, date=1781232101567, duration=184
type: 1 входящий, 2 исходящий, 3 пропущенный (бывают еще 4 голосовая почта, 5 отклоненный, 6 заблокированный). date хранит миллисекунды Unix-эпохи, duration хранит секунды. Подкинуть тестовый пропущенный звонок для проверки UI и убрать его за собой:

Код: Выделить всё

adb shell content insert --uri content://call_log/calls --bind number:s:+79990001122 --bind type:i:3 --bind date:l:1781240000000 --bind duration:i:0 --bind new:i:1
adb shell content delete --uri content://call_log/calls --where "number='+79990001122'"
Обратите внимание на тип l у date: миллисекундные метки не влезают в int, с типом i получите NumberFormatException.

SMS:

Провайдер content://sms с подпапками inbox, sent, draft. Прямой запрос на Android 10-15 от shell упирается в защиту:

Код: Выделить всё

adb shell content query --uri content://sms/inbox --projection address:date:body
Error while accessing provider:sms
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.telephony.SmsProvider ... requires android.permission.READ_SMS or android.permission.WRITE_SMS
У пользователя shell нет разрешения READ_SMS, это сделано намеренно. Рабочие варианты: рутованное устройство (глава 15) или эмулятор с образом AOSP, где работает adb root (глава 21):

Код: Выделить всё

adb shell su -c "content query --uri content://sms/inbox --projection address:date:body --sort 'date DESC'"
Row: 0 address=900, date=1781230011000, body=Perevod 5000r ot Ivan P.
Со вставкой вторая засада: начиная с Android 4.4 записи в SMS-провайдер от приложений, не назначенных SMS-приложением по умолчанию, игнорируются. root обходит и это:

Код: Выделить всё

adb shell su -c "content insert --uri content://sms/inbox --bind address:s:900 --bind body:s:TestSMS --bind date:l:1781240000000 --bind read:i:0"
События календаря:

Сначала смотрим, какие календари заведены на устройстве и их _id:

Код: Выделить всё

adb shell content query --uri content://com.android.calendar/calendars --projection _id:calendar_displayName:account_name
Row: 0 _id=1, calendar_displayName=qa.device01@gmail.com, account_name=qa.device01@gmail.com
Для вставки события провайдер требует минимум calendar_id, dtstart, dtend и eventTimezone, иначе словите IllegalArgumentException. Время задается в миллисекундах эпохи, текущее значение быстро получить на самом устройстве:

Код: Выделить всё

adb shell 'echo $(( $(date +%s) * 1000 ))'
1781245637000
Событие завтра с 10:00 до 11:00 по Москве:

Код: Выделить всё

adb shell content insert --uri content://com.android.calendar/events --bind calendar_id:i:1 --bind title:s:Standup --bind dtstart:l:1781334000000 --bind dtend:l:1781337600000 --bind eventTimezone:s:Europe/Moscow
Проверяем, вешаем напоминание за 10 минут (method 1 означает alert) и потом чистим:

Код: Выделить всё

adb shell content query --uri content://com.android.calendar/events --projection _id:title:dtstart --where "calendar_id=1 AND deleted=0" --sort "dtstart DESC"
Row: 0 _id=57, title=Standup, dtstart=1781334000000
adb shell content insert --uri content://com.android.calendar/reminders --bind event_id:i:57 --bind minutes:i:10 --bind method:i:1
adb shell content delete --uri content://com.android.calendar/events --where "_id=57"
Если на конкретной прошивке календарь отвечает Permission Denial, рецепт тот же: su -c или adb root на эмуляторе.

Медиа через MediaStore:

Медиатека живет за authority media. Тома: external_primary (встроенная память, Android 10+) и external (все хранилища разом). Картинки, видео, аудио и загрузки лежат по путям images/media, video/media, audio/media, downloads. Найти тяжелые фото:

Код: Выделить всё

adb shell content query --uri content://media/external/images/media --projection _id:_display_name:_size:relative_path --where "_size>5000000" --sort "date_added DESC"
Row: 0 _id=1041, _display_name=IMG_20260611_193412.jpg, _size=7204881, relative_path=DCIM/Camera/
Классическая проблема: запушили файл через adb push, а в галерее его нет, потому что MediaStore о нем не знает. Старый броадкаст MEDIA_SCANNER_SCAN_FILE на Android 11+ ненадежен, надежный способ: метод scan_volume у самого провайдера:

Код: Выделить всё

adb push wallpaper.jpg /sdcard/Pictures/
adb shell content call --method scan_volume --uri content://media/ --arg external_primary
Точечно работает scan_file с абсолютным путем, он возвращает URI новой записи. На части сборок он капризничает с нестандартными каталогами, кладите файлы в Pictures, DCIM, Movies, Download или откатывайтесь на scan_volume:

Код: Выделить всё

adb shell content call --method scan_file --uri content://media/ --arg /sdcard/Pictures/wallpaper.jpg
Result: Bundle[{uri=content://media/external_primary/images/media/1042}]
Зная URI, файл можно прочитать прямо через провайдер. Берите exec-out, а не shell, чтобы переводы строк не испортили бинарный поток (глава 14 про это уже предупреждала):

Код: Выделить всё

adb exec-out content read --uri content://media/external_primary/images/media/1042 > copy.jpg
adb shell content gettype --uri content://media/external_primary/images/media/1042
Result: image/jpeg
Удаление записи MediaStore сносит и сам файл, провайдер владеет своими данными:

Код: Выделить всё

adb shell content delete --uri content://media/external/images/media --where "_display_name='wallpaper.jpg'"
На устройствах с несколькими пользователями или рабочим профилем ко всем командам добавляется --user с id из pm list users (глава 7):

Код: Выделить всё

adb shell content query --user 10 --uri content://com.android.contacts/contacts --projection display_name
Частые ошибки и как их обойти:

Permission Denial. Текст исключения называет нужное разрешение. Если его нет у shell, поможет только su -c на рутованном устройстве или adb root на эмуляторе AOSP. На обычном продакшен-смартфоне adb root не сработает, сборка не debuggable.

Кавычки и пробелы. Аргументы проходят две оболочки, локальную и удаленную (подробный разбор в главе 22). Правило: весь вызов content в одинарные кавычки, значения с пробелами внутри в двойные. where со строками удобно класть в двойные кавычки снаружи, одинарные внутри: --where "name='user_rotation'".

NumberFormatException. Метки времени всегда привязывайте типом l (long), не i.

Could not find provider или Unknown URI. Опечатка в authority или пути. Список authority дает dumpsys package providers, точные пути и имена столбцов смотрите в контрактах SDK (ContactsContract, CallLog, Telephony, CalendarContract, MediaStore) либо запросите строку без --projection и прочитайте все столбцы из вывода.

Молчаливый успех. insert, update и delete при удаче ничего не печатают. Не верьте на слово, проверяйте контрольным query.

В главе 18 займемся резервным копированием, и провайдеры всплывут снова: то, что не попадает в adb backup, часто вытаскивается именно через content query. А в главе 24 эти команды станут рабочей лошадкой подготовки тестовых данных: быстрее способа накидать на устройство контакты, звонки и события перед прогоном UI-тестов просто нет.
👍3 ❤️3 🔥2 😄 🤔1
✔ Лучший ответ сформирован автоматически — slevin
android_roman писал(а):У пользователя shell нет разрешения READ_SMS, это сделано намеренно. а на эмуляторе с образом Google Play су нет и adb root пишет adbd cannot run as root in production builds. получается смс там вообще никак не глянуть? качать отдельный AOSP образ только ради этого как-то тоскливо, может есть трюк попроще
Перейти к ответу →
Аватара пользователя
slevin
Сообщения: 1
Зарегистрирован: 12 май 2026, 07:24

Re: Команды для поставщиков контента (content)

Сообщение slevin »

✔ Лучший ответ — сформирован автоматически
android_roman писал(а):У пользователя shell нет разрешения READ_SMS, это сделано намеренно.
а на эмуляторе с образом Google Play су нет и adb root пишет adbd cannot run as root in production builds. получается смс там вообще никак не глянуть? качать отдельный AOSP образ только ради этого как-то тоскливо, может есть трюк попроще
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
spark_andy
Сообщения: 1
Зарегистрирован: 21 май 2026, 20:55

Re: Команды для поставщиков контента (content)

Сообщение spark_andy »

подтверждаю момент с контактами, гонял на Redmi Note 13 (HyperOS): после insert в raw_contacts контакт нигде не виден, появился только когда добавил строку с mimetype name. сначала грешил на MIUI, оказалось так и задумано. и да, caller_is_syncadapter=true реально удаляет насовсем, без него удаленный контакт у меня воскрес после синка с гуглом
👍2 ❤️ 🔥 😄 🤔
Аватара пользователя
terraformlover
Сообщения: 2
Зарегистрирован: 14 май 2026, 00:22

Re: Команды для поставщиков контента (content)

Сообщение terraformlover »

на Samsung S24 (One UI 6.1) scan_file для файла в Download стабильно возвращал Bundle с uri=null, а scan_volume отрабатывал нормально. так что совет про откат на scan_volume не для галочки, проверено на своей шкуре
👍1 ❤️ 🔥1 😄 🤔1
Аватара пользователя
seniorphoenix
Сообщения: 1
Зарегистрирован: 16 май 2026, 15:54

Re: Команды для поставщиков контента (content)

Сообщение seniorphoenix »

а content query умеет какой-нибудь limit? на медиатеке в 8 тысяч фоток вывод просто убивает терминал, приходится костылить через --where "_id>..." и руками листать. и спасибо за прием с echo $(( $(date +%s) * 1000 )), а то я каждый раз лазил на epochconverter
👍1 ❤️2 🔥1 😄 🤔
Ответить
← Предыдущая глава
Модификация системных настроек через settings
Следующая глава →
Резервное копирование и восстановление (backup)

Все главы курса «ADB: Android Debug Bridge»

Поделиться темой: ✈ Telegram VK

Вернуться в «ADB: Android Debug Bridge»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость