Какие изменения Android 14 могут вас задеть

Это просто подбивка списка изменений с пояснениями, чтобы не скакать по статьям на официальном сайте. Хотя правильнее, конечно, читать оригинал.

Изменения, касающиеся всех приложений

Здесь собраны изменения, которые задевают приложения, даже если их таргет ниже 14. Называется новая версия, если что, Android Upside-down cake — “перевёрнутый” пирог или “вверх ногами” или “наизнанку” — как угодно. В общем, суть в том, что после выпекания этот пирог переворачивают и то, что раньше было верхом пирога становится его дном.

Core функциональность

Разрешение SCHEDULE_EXACT_ALARM больше не выдаётся автоматически

Exact alarms — это когда нужно выполнить задачу в строго заданное время, ни раньше, ни позже. К примеру — напомнить о встрече. Начиная с Android 14 для большинства устанавливаемых приложений (очевидно, не касается уже установленных), таргет которых начинается с 13, пермишен SCHEDULE_EXACT_ALARM автоматически выдаваться больше не будет. В общем, теперь нужно спрашивать его явно.

О чём речь

  • SCHEDULE_EXACT_ALARM появилось в API level 31 (это S, он же 12)
  • без этого разрешения запланированные задачи будут выполняться не в строго заданное время, а когда ОС разрешит
  • его нужно (теперь, когда представили – не нужно было) запрашивать. Сценарий будет не как с runtime permission — диалог — а как разрешение на установку приложений, когда пользователь на отдельном экране дёргает переключатель
  • этого экрана может не быть вовсе. Вообще все экраны “специальных разрешений” имеют пометку, что производитель прошивки может его убирать. Имейте в виду.
  • и пользователь, и ОС могут переключатель выставить в отключенное состояние, отозвав разрешение
    • отзыв разрешения автоматически удаляет все запланированные задачи
  • в API level 33 (это TIRAMISU, он же 13) у этого разрешения появилась альтернатива USE_EXACT_ALARM
    • это разрешение выдаётся автоматически
    • пользователь не может его отозвать
    • вас выбросят из Google Play за использование этого разрешения, если приложение не является будильником или чем-то подобным, чему реально нужно срабатывать секунда в секунду. Я смотрел весь сериал только из-за этой гифки

Контекстно-зарегистрированные бродкасты ставятся в очередь для кешированных приложений

Если приложение перешло в состояние “кешировано” (то есть оно не на переднем плане и не убито системой и становится кандидатом на отстрел), то контекстные бродкасты складываются в очередь. Если приложение не будет отстрелено системой и пользователь снова к нему обратиться, то лежащие в очереди бродкасты будут доставлены, если контекст позволяет.

О чём речь

Контекстно-зарегистрированные бродкасты, это те, которые были зареганы в контексте чего-то. Например, в контексте активити. То есть если активити приложения умерло, то и бродкаст больше не придёт. Если бродкаст был зареган в апликейшен контексте, то будет доставлен, пока жив хоть какой-то компонент приложения


Приложения могут убить только собственные фоновые процессы

Метод killBackgroundProcesses() может убить теперь только фоновые процессы того приложения, из которого был вызван. Наконец сдохнет большинство “оптимизаторов батареи”. К сожалению, во-первых только сторонние, во-вторых не все. Вендорский шлак так и будет существовать, а разработчики стороннего ПО то и дело будут ссылаться на https://dontkillmyapp.com/

О чём речь

  • это древняя апишка, которая защищена разрешением KILL_BACKGROUND_PROCESSES. Само её существование для сторонних приложений — бред какой-то
  • эта апишка не позволяла убивать системные приложения, только сторонние
  • патч безопасности от 1 декабря 2022 уже ограничил работу пермишена KILL_BACKGROUND_PROCESSES — стало нельзя убивать сторонние процессы
  • теперь это ограничение, которое раньше было только фиксом безопасности, затащили в ОС. Другими словами, изменение произошло в конце прошлого года на всех актуальных версиях ОС
  • некоторые оптимизаторы использовали не эту апишку, а работали хитрее через Accessibility Service. Так что готовьтесь к тому, что шлак массово будет переходить на это, очень опасное, API

Безопасность

Поднят минимальный таргет для устанавливаемых приложений

Приложения с targetSdkVersion ниже 23 (это Android 6) больше не могут быть установлены. То есть в Android 14 теперь все приложения будут поддерживать runtime permission, либо не будут работать вовсе. Если приложение с более низким таргетом было установлено, когда на ваше устройство прилетел Android 14, то для него будет сделано исключение и оно продолжит работать. Но вот переустановить его уже нельзя будет.


Поле Media owner package name может быть изменено

К хранилищу медиа можно делать запросы поля OWNER_PACKAGE_NAME, в котором будет (или не будет — может быть NULL) указано имя пакета, который сохранил определённый медиа файл. Начиная с Android 14 значение этого поля будет подменяться для всех приложений кроме (должно быть выполнено хоть одно условие):

О чём речь

Ничего не понятно, правда?

  • запросы к медиа хранилищам делаются как запросы к БД. Пусть они и обёрнуты в некое API, сути это не меняет
  • запросить можно в том числе “а кто создал этот файл”: OWNER_PACKAGE_NAME. В ответ придёт или имя пакета приложения, или NULL
  • Google решили, что через такие запросы можно понять, какие приложения установлены на устройстве. И это, надо сказать, разумно. Вполне себе тянет на утечку по сторонним каналам. А ведь уже давно запрос списка установленных приложений защищается пермишеном QUERY_ALL_PACKAGES
  • сложив два и два, Google сделали так. Если приложение, которое пытается узнать владельца файла через эту апишку, не владеет разрешением QUERY_ALL_PACKAGES, то проверяется, является ли владелец файла всегда видимым приложением
    • это всякие системные приложения — они ведь всегда есть, чего их скрывать
    • приложения, которые установило ваше собственное приложение. Оно и так знает, что установило, чего скрывать
    • приложение, которое вызывало ваше через startActivityForResult(). Это ведь оно вас позвало, оно и так уже призналось в своём существовании
    • всякое другое, типа контент провайдеров, клавиатур. Подробнее тут, если надо
  • в итоге, если владелец не входит в список всегда видимых && запрашивающий не владеет QUERY_ALL_PACKAGES, то он не сможет узнать владельца. Как именно подменяется владелец я не смотрел. Да и не важно, хоть там будет просто android написано, хоть там будет NULL. Главное, что нельзя таким образом составить список установленных приложений

User experience

Давайте уже согласимся, что говорить “юикс” нормально, а говорить “пользовательский опыт” — это ментальное заболевание. Впрочем, лично мне нравится говорить “привычки”, хотя в реальной речи стараюсь не использовать это слово. Оно нравится мне, но другие как-то так не говорят и боюсь, что буду сбивать с толку других

Взаимодействие с несмахиваемыми уведомлениями

В Android 14 пользователь может смахивать уведомления, которые ранее были не смахиваемыми, но в конкретных случаях. Если уведомление сделано не смахиваемым установкой флага FLAG_ONGOING_EVENT, то пользователь теперь может всё-таки его смахнуть. Флаг выставляется, например, через NotificationCompat.Builder#setOngoing(boolean).

Такие уведомления всё равно не будут смахиваться, если:

Поведение не меняется, если:

О чём речь

Подавляющее большинство моих знакомых (и я сам) в первую очередь подумали — касается ли это foreground service? Если вы знаете ответ заранее — снимаю шляпу.

  • мы привыкли, что уведомления фореграунд сервисов не смахивались, в общем-то
  • в Android 13 поведение изменилось. Уведомления от фореграундов стало можно смахивать
  • чтобы всё же закрепить уведомление, нужно ручками выставлять флаг Notification#FLAG_ONGOING_EVENT, о котором говорилось выше. По умолчанию билдер за вас его не выставит
  • в Android 14 уже пошли дальше и флаг тоже почикали

Предоставление частичного доступа к фото и видео

Если вам достаточно фото пикера, то можно пропускать.

В Android 13 появились пермишены доступа к изображениям (READ_MEDIA_IMAGES) и видео (READ_MEDIA_VIDEO). В Android 14 диалог запроса разрешения изменился:

Ну и видно, что разрешение READ_MEDIA_AUDIO не задето.

О чём речь

  • в Android 13 добавились новые разрешения на доступ к медиа, которые нужно использовать вместо READ_EXTERNAL_STORAGE: READ_MEDIA_IMAGES, READ_MEDIA_VIDEO и READ_MEDIA_AUDIO
  • если приложение, которое работает с этими разрешениями, будет установлено в Android 14, то диалог запроса разрешений изменится — добавится новая кнопка
    • то есть мы понимаем, что минимально возможный таргет будет Android 13. Ведь до этого таких разрешений не было
  • теперь пользователь может нажать новую кнопку и дать доступ не ко всем картинкам, а к конкретным
  • если пользователь сделает выбор конкретных, то это разрешение сработает примерно как one time permission — то есть будет выдано на текущую жизнь прилаги. После отстрела и нового запроса разрешения диалог появится вновь
  • разрешение всего и запрет всего работают как раньше
  • это поведение можно сделать немного прозрачнее в первую очередь для себя самого, благодаря новому пермишену READ_MEDIA_VISUAL_USER_SELECTED, но оно доступно только для target 14. Впрочем, в этом случае лучше использовать именно пикер, а не изобретать свой велосипед

Специальные возможности

Масштабирование шрифта до 200%

Система позволит задавать масштабирование шрифта до 200%. Будьте добры убедиться, что ваше приложение к этому готово.

Признаюсь, я ни разу не видел приложений, которые готовы хотя бы к небольшому увеличению размеров шрифтов. Всё сразу начинает ехать, перестаёт влезать. В общем, дизайнеры будут класть болт на людей со слабым зрением на 200% яростнее.


Изменения, стреляющие для target на 14 и выше

Некоторые из изменений ниже касаются приложений с таргетом и ниже 14. Но поддержка 14 позволяет управлять поведением более точно.

Core функциональность

Для foreground services требуется указание типа

Для приложений с targetSdkVersion на Android 14 для каждого foreground service нужно указывать его тип из заранее предопределённого списка. Если для приложения нельзя выбрать логичный тип из списка, то нужно переехать на WorkManager или специально созданный user-initiated data transfer job. Не уверен, что это нужно переводить. Вроде понятно, что речь идёт о задачах, которые инициировал сам пользователь.

О чём речь

  • для таргета на Android 10 для сервисов в манифесте стало нужно указывать тип android:foregroundServiceType для некоторых задач: камеры, геолокации. Если сервис не использовался для этих целей, то и тип не нужен
  • для таргета на Android 14 тип указать обязан и никак иначе. На выбор даётся более десятка типов (звонки, камера, локация, здоровье, микрофон и другое). Фореграунд сервиса без типа быть теперь не может
  • если для какого-то сервиса не подходит ни один тип, то у вас два пути:
    • переход на WorkManager. С этим, думаю, всё понятно, ему лет сто уже. Если не знакомы — ознакомьтесь, сбросьте часть рутины. Но добавите новую зависимость. А мы знаем, что Google делает со своими либами каждые несколько лет
    • переход на “задачи передачи данных, инициированных пользователем”: user-initiated data transfer jobs
  • пользовательские задачи — это всякие длинные задачи, которые пользователь инициировал явно, но которые не подходят по списку типов. К примеру, скачивание файла с удалённого ресурса или копирование файлов на внешний носитель
    • трансфер джобы обязывают объявить пермишен RUN_LONG_JOBS. Читайте так: в следующей версии или через пару тоже начнут ограничивать. Да и Google Play может начать делать кислые щи
  • есть некоторая защита от того, что вы впишете тип хоть какой-нибудь, чтобы от вас отстали. Для этого каждому типу сопоставлено разрешение, которое нужно объявить в манифесте. А лепить разрешения “чтобы отстали” уже не хочется
  • есть отдельный тип Short service, который может быть полезен для реально коротких задач. Он не требует отдельного разрешения. Однако жизнь этого сервиса ограничена ~3 минутами, которые начинают тикать с вызова startForeground() и заканчивают в момент остановки сервиса (можно selfStop(), можно stopForeground(). Если не уложились в 3 минуты и не остановили сервис, ловите ANR. Кроме того одновременно может работать только один короткоживущий сервис. Попытка запустить ещё один приведёт к исключению ForegroundServiceStartNotAllowedException

Безопасность

Ограничения неявных и отложенных интентов

Мы все говорим “интент”. А вот говорите вы “отложенный” или “пендинг”?

Приложения с таргетом на Android 14 получают изменённое поведение взаимодействия со своими собственными компонентами:

О чём речь

Вроде и понятно, а вроде не очень, да?

  • компонентам приложения (активити, сервисы, вот это всё) в манифесте нужно выставлять флаг экспорта android:exported
  • документация нам говорила, что экспортированные (android:exported="true") компоненты доступны всем приложениями на подёргать. Если хотите экспортированный, но защищённый, для вас есть специальный механизм разрешений
  • если компонент не экспортированный, то дёргать его можно только изнутри этого же приложения (более широко — от этого же UID). И привилегированные системные компоненты ещё могли войти в этот сарай (отсылка для поехавших). Если же левое приложение попытается дёрнуть такой компонент, то получит исключение, что нет такого

Повторим ещё раз. Изнутри приложения можно дёргать свои собственные не экспортированные компоненты. Никаких ограничений тут нет

  • многие разработчики делали не экспортированные, например, активити и для него создавали фильтр вида my.mega.app.action.NAME. Ну и далее из кода context.startActivity(my.mega.app.action.NAME)
  • однако (это частный пример) авторы вредоносных приложений стали создавать экраны, которые выглядят точно также, как те, на которые они нацелились. И создавали такие же фильтры. Теперь система не знает, кому послать интент, он же не явный. Выбор из настоящего экрана и поддельного перекладывается на пользователя и тот может сделать неправильный выбор. То есть внешние приложения по-прежнему не могут вызвать не экспортированные компоненты, но могли прикинуться теми приложениями
  • теперь, если таргет Android 14, для вызова своего собственного не экспортированного компонента нужно создавать явный интент. Что не позволит уже уйти этому интенту не в те ворота

Бродкаст ресиверы, зарегистрированные в рантайме, должны явно указать эспортированность

Контекстные ресиверы (о них было выше) в приложениях с таргетом на Android 14 обязаны явно указать, экспортированы они (RECEIVER_EXPORTED) или нет (RECEIVER_NOT_EXPORTED). При регистрации слушателя системных бродкастов (к примеру переход в режим полёта или установка приложения) это указание не требуется. Всё равно системные послать кто угодно не может — считается, что защита уже есть.


Более безопасная динамическая загрузка кода

Если приложение имеет таргет Android 14 и использует динамическую загрузку кода, то есть буквально передаёт внешнюю либу загрузчику, то теперь нужно помечать файл с этим кодом доступным только для чтения: File#setReadOnly()

Ну и отдельно Google просит (но не проверяет) явно пересоздавать эти файлы. Не проверять, что раз файл есть, то не перекачивать. А прям удалять и качать его снова. Видимо идея в том, чтобы вам не подложили левый файл и вы не приняли его за свой собственный. Ну и просят проверять подпись файла.

В общем-то суть сводится к старой истории, когда одно приложение качало недостающие компоненты со своего сайта в общедоступную папку, а троян на устройстве пользователя просто клал заранее подготовленный файл и приложение жрало его как родной.


Новые ограничения запуска активити из фона

Для приложений с таргетом на Android 14, система накладывает ограничения на запуск активити из фона:

О чём речь

Запутанно из-за всех этих “оно”, “его”. Кто, кому, чего? Признаюсь, перечитывал описание раз 10 и каждый раз понимал по-разному.

Сначала с PendingIntent

  • это такой вид интента, который можно передать другому приложению так, будто ты его внутри себя перебрасываешь. И получатель теперь может дёргать твои компоненты от твоего имени. То есть создаёшь интент на запуск экрана. Оборачиваешь его в пендинг интент и передаёшь этот пендинг внешнему приложению. Теперь внешнее приложение может запустить твой экран от твоего же имени
  • при создании объекта отложенного интента нужно заранее решить, позволяем ли получателю поднимать наши экраны из фона. Можем разрешить, запретить или оставить на усмотрение системы
  • метод пришёл на смену setPendingIntentBackgroundActivityLaunchAllowed, который появился в 13 и сдох в 14. Впрочем, его нигде и не афишировали. И судя по исходникам, он вообще скрыт. Видимо планировали, но не доделали

Теперь с биндингом к сервису

  • наше приложение видимо пользователю
  • есть другое приложение, которое предоставляет сервис и сейчас в фоне где-то
  • наше приложение биндится к сервису второго приложения
  • раньше это второе приложение получало возможность запускать активити из фона. Типа, ко мне же подключилось видимое приложение
  • теперь поведение изменилось. Разрешение на запуск активити не предоставляется. Приложение, которое биндится, должно явно передать BIND_ALLOW_ACTIVITY_STARTS

Обновлены ограничения не SDK интерфейсов

Начиная с Android 9 урезают доступ к разным API. Часть держат в белом списке, часть в сером (типа, готовьтесь, можем отрезать), к части доступ заблокировали. Каждый релиз список пополняется. Вот, снова пополнили. Сейчас он весит больше 50 Мегабайт в csv формате.

Пример одной строки, как эти ограничения описаны:

Landroid/Manifest$permission ->BIND_COMPANION_DEVICE_SERVICE:Ljava/lang/String public-api sdk system-api test-api

В этих ограничениях меня забавляет то, что куча либ самого Google, из джет пака, обращаются к серым API.


Не знаю как вам, а мне изучение было полезным. А то я уже несколько релизов пропустил.