В своей программе я использую вызов настроек телефона.
Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
startActivity(intent);
Это просьба открыть нужное место в системных настройках телефона
Торопыги могут сказать, что я забыл этот вызов взять в try-catch и будут не правы.
Ошибка была не в том, что службы печати нет на телефоне, а гораздо дальше.
Телефон открывает список всех установленных служб как и положено.
А вот после клика по имени любой службы, даже по службе от самого андроида получаем креш.
Скрытый текст
Под спойлером скриншоты
Но такая глобальная ошибка не могла остаться в релизе. Если мы попробуем попасть в настройки штатным путем (свап по экрану сверху - шестеренка - искать печать - и далее)
Скрытый текст
То все работает. Но что это такое ? Внешний вид разный !
Получается вендор не стал полностью кастомизировать штатные настройки и пропустил его так как есть альтернатива.
Проблема возникла по причине смены дизайна родительской активити для этого фрагмента. В штатном вот этот кусок отрабатывает правильно https://github.com/GZOSP/packages_apps_settings/blob/11.0/src/com/android/settings/print/PrintServiceSettingsFragment.java#L354 , а в переделаном дизайне меню (... в правом верхнем углу) не предусмотрено.
Первая мысль надо искать кастомный интент (для торопыг Ошибка там использован штатный)
Начинаю гуглить про Realme UI и какие нибудь кастомные интенты с печатью - пусто. Про Color OS - пусто.
Полное не понимание, а куда дальше копать ?
Решаю посмотреть пакеты.
> adb shell pm list packages | grep setting
package:com.android.settings.intelligence
package:com.oplus.wirelesssettings
package:com.android.settings
package:com.android.providers.settings
>adb shell pm list packages | grep print
package:com.google.android.printservice.recommendation
package:com.android.systemui.overlay.fingerprint.anim.lgsy
package:com.android.systemui.overlay.fingerprint.anim.jslz
package:com.android.systemui.overlay.fingerprint.anim.jhsy
package:com.android.printspooler
package:com.android.systemui.overlay.fingerprint.anim.xklc
package:com.android.systemui.overlay.fingerprint.anim.ccyh
package:com.android.systemui.overlay.fingerprint.anim.nlgs
package:com.smart.printer.print.photos.documents.printing.pictures
package:com.android.systemui.overlay.fingerprint.anim.tyjw
Идей не добавляется. Но ВОТ же !! есть где-то код, который работает. Но ничего похожего на настройки от самого реалми не видно.
!!! Так !!! Если я вижу правильное место, то оно точно есть !!!
Как узнать какое приложение на топе стека активити ? гуглим
adb shell dumpsys window | grep Focused.
mTopFocusedDisplayId=0
mFocusedApp=ActivityRecord{37325a8 u0 ru.a402d.printservice/.ui.MainActivity t4128}
mLastFocusedRootTask=Task{a1d27a7 #4128 type=standard A=10319:ru.a402d.printservice}
mFocusedWindow=Window{22f4af7 u0 Application Error: com.android.settings}
DO DUMP StrategyManager : { StrategyIgnoreSleepKey StrategyShutdown StrategyLaunchIntercomCustom StrategyCustomizeIgnoreVirtualKey StrategyUnusedPhoneDetection StrategyBlackscreenshot StrategyPowerKeyEndCall StrategyDisableBottomKey StrategyVolumeKeyLaunchCamera StrategyIngoreKeyInFocusedWindow}
mQueueingInterceptorsCopy : [120:com.android.server.policy.KeyLongPressBaseStrategy$1@64b501c, 130:com.android.server.policy.StrategyCustomizeIgnoreVirtualKey$1@9f7c21a, 170:com.android.server.policy.StrategyDisableBottomKey$2@221d425, 190:com.android.server.policy.StrategyIngoreKeyInFocusedWindow$1@a8c9fa]
mDispatchingInterceptorsCopy : [120:com.android.server.policy.KeyLongPressBaseStrategy$1@64b501c, 130:com.android.server.policy.StrategyCustomizeIgnoreVirtualKey$1@9f7c21a, 170:com.android.server.policy.StrategyDisableBottomKey$2@221d425, 190:com.android.server.policy.StrategyIngoreKeyInFocusedWindow$1@a8c9fa]
mFocusedApp=ActivityRecord{461677f u0 com.android.printspooler/.ui.settings.SettingsEntranceActivity t4127}
mLastFocusedRootTask=Task{9e02046 #4127 type=standard A=1000:com.android.settings.root}
mFocusedWindow=Window{f727c67 u0 com.android.printspooler/com.android.printspooler.ui.settings.SettingsEntranceActivity}
mTopFocusedDisplayId=0
Оказывается из настроек мы незаметно для себя попадаем в спулер печати com.android.printspooler
А что же тогда в его манифесте ? Будем смотреть
adb shell pm path com.android.printspooler
package:/system_ext/app/PrintSpooler/PrintSpooler.apk
Узнали где лежит, теперь скачаем к себе
adb pull /system_ext/app/PrintSpooler/PrintSpooler.apk
По быстрому заглянуть в манифест приложения, можно просто открыв apk в android studio.
<activity
android:theme="@ref/0x7f11002a"
android:label="@ref/0x7f100135"
android:name="com.android.printspooler.ui.settings.SettingsEntranceActivity"
android:exported="true"
android:screenOrientation="-1"
android:configChanges="0x40003dfe"
android:windowSoftInputMode="0x22">
.....
<intent-filter
android:priority="1">
<action
android:name="android.settings.ACTION_PRINT_SETTINGS" />
<category
android:name="android.intent.category.DEFAULT" />
</intent-filter>
...
</activity>
Оказывается нет ни какого специального интента.
Часть вторая. Как вызвать нужный компонент
И начнем мы ее решать с модификации своего манифеста приложения
<queries>
...
<intent>
<action
android:name="android.settings.ACTION_PRINT_SETTINGS" />
<category
android:name="android.intent.category.DEFAULT" />
</intent>
...
</queries>
https://developer.android.com/guide/topics/manifest/queries-element
Начиная с апи 30 (11-й андроид) для секьюрности урезали доступ к списку установленных приложений и ресолвить доступные для выполнения намерения теперь можно только упомянув в манифесте.
По дефолту через startActivity запускается самая подходящая программа. Есть способ предложить пользователю выбрать из нескольких
PackageManager packageManager = requireActivity().getPackageManager();
Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, getString(R.string.open));
if (intent.resolveActivity(packageManager) != null) {
startActivity(chooser);
}
Этот способ показывает выбор, но только, если пользователь в предыдущий раз не поставил [v] использовать всегда.
В моем случае диалог не был показан, а все также открывался экран приводящий к крешу.
Хорошо, проверяем, что ресолвятся несколько компонентов
PackageManager packageManager = requireActivity().getPackageManager();
Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent,
PackageManager.GET_RESOLVED_FILTER);
if (resolveInfos.size() > 1) {
// ок их тут много
}
Уточнить нужный для запуска можно так
String packageName = resolveInfo.activityInfo.applicationInfo.packageName;
intent.setPackage(packageName);
Время костылить
Из-за вот таких чудес производителей телефонов, да и просто из-за отличий в версиях андроида реальный код обрастает подпорками.
Делаю следующие предположения. Если для события больше одного обработчика, то производитель ввел его намеренно и нужно использовать его. На случай , если их три или больше, то буду брать первый отличный от штатного.
Окончательный код принимает такой вид.
PackageManager packageManager = requireActivity().getPackageManager();
Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent,
PackageManager.GET_RESOLVED_FILTER);
try {
if (resolveInfos.size() > 1) {
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo.activityInfo != null) {
String packageName = resolveInfo.activityInfo.applicationInfo.packageName;
if (!"com.android.settings".equals(packageName)) {
intent.setPackage(packageName);
break;
}
}
}
}
} catch (Exception ignored) {}
try {
startActivity(intent);
} catch (Exception e) {
Toast.makeText(requireActivity(), R.string.Oopppsss, Toast.LENGTH_SHORT).show();
}