Некоторые операции в Android 10 невозможно выполнять так же, как в Android 6. В этой статье мы собрали несколько советов для адаптации вашего приложения под Эвоторы с ОС на базе Android 10 (EvotorOS 5.x.x).
Чтобы получить IMEI устройства, используйте метод getImei():
public String getImei (int slotIndex)
В актуальных версиях Android этот метод отмечен как устаревший (deprecated) и запрещён для пользовательских приложений. В ОС Эвотор этот метод доступен, но нужны разрешения READ_PHONE_STATE
и READ_PRIVILEGED_PHONE_STATE
.
При первом запуске приложение должно запрашивать доступ к отдельным функциям телефона. Например, к камере, микрофону и контактам. Если приложение устаревшее и не запрашивает разрешений, то оно может работать некорректно. В таких случаях пользователи должны вручную включить нужные разрешения в настройках устройства.
Для корректной работы приложения на Android 10 разработчик должен адаптировать его самостоятельно. Если приложению нужен доступ только к камере, местоположению, микрофону и календарю, то ничего делать не нужно — разрешения выдаются автоматически.
В Android 10 по умолчанию нет доступа к /sdcard
, его нужно включать вручную. В ОС Эвотор для Android 10 это делать не нужно, /sdcard
будет доступен без дополнительных настроек так же, как на ОС Эвотор версии 4.х.
Андроид 10 ограничивает работу приложений в фоновом режиме для максимальной автономности устройства. Чтобы приложение могло работать в фоновом режим, используйте метод startForeground():
public final void startForeground (int id,
Notification notification)
Также обязательное требование — push-уведомление о том, что в фоновом режиме работает приложение.
Адаптировать приложение под требования Android 10 разработчику нужно самостоятельно. В ОС Эвотор 5.х возможно только скрыть все уведомления через флаг FLAG_HIDE_NOTIFICATION
:
private const val FLAG_HIDE_NOTIFICATION = 0x10000000
notification.flags = notification.flags or FLAG_HIDE_NOTIFICATION
Используйте IMPORTANCE_DEFAULT
вместо IMPORTANCE_NONE
. Фрагмент кода для примера:
@RequiresApi(Build.VERSION_CODES.O) // API >= 26
private static String createNotificationChannel(Context context, String channelId, String channelName) {
NotificationManager nm = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
if (nm != null) {
if (nm.getNotificationChannel(channelId) != null) {
return channelId;
}
NotificationChannel nc = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
nc.setLightColor(BLUE);
nc.setLockscreenVisibility(VISIBILITY_PRIVATE);
nm.createNotificationChannel(nc);
}
return channelId;
}
В Эвотор ОС есть широковещательное сообщение (broadcast) при подключении и отключении HDMI. Возможно два состояния: "newState" = "connected" / "disconnected"
Широковещательное сообщение:
ru.evotor.action.HDMI_STATUS_CHANGED
Эвотор Power может выводить информацию на встроенный или внешний экран. Встроенный экран — internal
, используется по умолчанию, внешний экран — external
. На выбранном экране будут отображаться панели уведомлений и навигации.
Метод для определения текущего основного экрана:
getWindowManager().getDefaultDisplay().getName()
Возможный результат:
HDMI
— внешний экран, подключенный через HDMI;Internal
— встроенный экран Эвотор Power.Пример кода для захвата окна на дополнительном экране без блокирования панели навигации:
public static final int SYSTEM_UI_FLAG_IMMERSIVE_HIDDEN = 64; //0x40
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // скрыть NavBar
| View.SYSTEM_UI_FLAG_FULLSCREEN // скрыть StatusBar
| View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| SYSTEM_UI_FLAG_IMMERSIVE_HIDDEN); //0x40 <-- блокирует NavBar и StatusBar от вытягивания если они спрятаны предыдущими флагами
Пример кода для отключения функции автоматического уменьшения яркости:
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
Эвотор Power может работать в режиме ФР или СТ. Текущий режим работы нужно учитывать в приложении. ОС один раз отправляет в приложения intent
с обозначением текущего режима. После перезагрузки текущий режим нужно получать из API.
Пример получения текущего режима из API:
package ru.evotor.framework.system.mode
object DeviceModeApi
............
/**
* Возвращает текущий режим работы терминала, если применимо, иначе - null
*/
@JvmStatic
fun getCurrentDeviceMode(context: Context): String? {
private fun startCameraX() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(
{
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)
// Preview
val preview = Preview.Builder()
.setTargetResolution(
Size(
profile.videoFrameHeight,
profile.videoFrameWidth
)
)
.build().also {
it.setSurfaceProvider(cameraPreviewContainer.surfaceProvider)
}
val scannerAnalyzer = ScannerAnalyzer {
val rectangle = Rect()
val window = window
window.decorView.getWindowVisibleDisplayFrame(rectangle)
val statusBarHeight = rectangle.top
val contentViewTop =
window.findViewById<View>(Window.ID_ANDROID_CONTENT).top
val titleBarHeight = contentViewTop - statusBarHeight
it.boundingBox?.toRectF()?.also {
it.offset(0f, titleBarHeight.toFloat())
val rectView = RectView(this, it)
cameraPreviewContainer.addView(rectView)
}
it.rawValue?.let {
setResult(Activity.RESULT_OK, putScannerResult(Intent(), it))
finish()
}
}
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(OUTPUT_IMAGE_FORMAT_YUV_420_888)
.setTargetResolution(
Size(
profile.videoFrameHeight,
profile.videoFrameWidth
)
)
.build().also {
it.setAnalyzer(cameraExecutor, scannerAnalyzer)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
val point = SurfaceOrientedMeteringPointFactory(
profile.videoFrameHeight.toFloat(),
profile.videoFrameWidth.toFloat(),
imageAnalyzer
).createPoint(
profile.videoFrameHeight / 2f,
profile.videoFrameWidth / 2f
)
autoFocus(point)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
},
ContextCompat.getMainExecutor(this)
)
}
private fun autoFocus(point: MeteringPoint) {
camera?.cameraControl?.startFocusAndMetering(
FocusMeteringAction.Builder(point).setAutoCancelDuration(2, TimeUnit.SECONDS).build()
)?.addListener(
{
Thread.sleep(500)
autoFocus(point)
},
Executors.newSingleThreadExecutor()
)
}