I'm particularly interested in high contrast text, color correction, and magnification settings. I did some research online, couldn't find what I want. I saw one answer about detecting high contrast text:
AccessibilityManager am = (AccessibilityManager) this.getSystemService(Context.ACCESSIBILITY_SERVICE);
boolean isHighTextContrastEnabled = am.isHighTextContrastEnabled();
But somehow it gives me the error for isHighTextContrastEnabled() saying that it is undefined for the type AccessibilityManager.
Also didn't find solution for the other two settings detection.
AccessibilityManager am = (AccessibilityManager) this.getSystemService(Context.ACCESSIBILITY_SERVICE);
Class clazz = am.getClass();
Method m = null;
try {
m = clazz.getMethod("isHighTextContrastEnabled",null);
} catch (NoSuchMethodException e) {
Log.w("FAIL", "isHighTextContrastEnabled not found in AccessibilityManager");
}
Object result = null;
try {
result = m.invoke(am, null);
if (result != null && result instanceof Boolean) {
Boolean b = (Boolean)result;
Log.d("result", "b =" + b);
}
} catch (Exception e) {
android.util.Log.d("fail", "isHighTextContrastEnabled invoked with an exception" + e.getMessage());
return;
}
and I do test, it return false, so it works
What a did was
private fun checkForAcessibility(): Boolean {
try {
val accessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val accessibilityManagerClass = accessibilityManager.javaClass
val isHighTextContrastEnabledMethod = accessibilityManagerClass.getMethod("isHighTextContrastEnabled")
val result: Any = isHighTextContrastEnabledMethod.invoke(accessibilityManager) ?: return AccessibilityEnabledValue.ERROR_QUERYING_VALUE
if (result !is Boolean) {
return AccessibilityEnabledValue.ERROR_QUERYING_VALUE
}
return if (result) {
AccessibilityEnabledValue.TRUE
} else {
AccessibilityEnabledValue.FALSE
}
} catch (e: Exception) {
return AccessibilityEnabledValue.ERROR_QUERYING_VALUE
}
}
enum class AccessibilityEnabledValue(val value: String) {
TRUE("true"),
FALSE("false"),
ERROR_QUERYING_VALUE("error_querying_value")
}
I've noticed that isHighTextContrastEnabled() method does not contain parameters.
use this for color correction:
int color_correction_enabled = 0;
try {
color_correction_enabled = Settings.Secure.getInt(this.getContentResolver(), "accessibility_display_daltonizer_enabled");
} catch (Exception e) {
color_correction_enabled = 0; // means default false
}
Though reflection solution helps with some devices I was unable to get "isHighTextContrastEnabled" via reflection on my OnePlus 5 device.
So I ended up with another method when reflection fails. Just rendering text on a bitmap and then manually checking if there is a specific color (different from black and white) in the bitmap (cause in "High Contrast" mode it will be black and white).
Here is the code in Kotlin:
private fun isHighTextContrastEnabledWithBmp(): Boolean {
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
val testColor = Color.GREEN
val paint = Paint().apply {
color = testColor
textSize = 5f
}
canvas.drawText("1", 0f, 10f, paint)
val pixels = IntArray(bmp.width * bmp.height)
bmp.getPixels(
pixels, 0, bmp.width,
0, 0, bmp.height, bmp.width
)
val result = pixels.any { it == testColor }.not()
return result
}
Using androidx.core:core-ktx's getSystemService extension,
isHighTextContrastEnabled = runCatching {
context.getSystemService<AccessibilityManager>()?.let {
(it.javaClass.getMethod("isHighTextContrastEnabled").invoke(it) as? Boolean)
}
}.onFailure { it.printStackTrace() }.getOrNull() ?: false
Related
I am using MediaProjectionManager for taking screenshots from the ForegroundService. I discovered that the behavior of capturing surface works differently in Android 10 and Android 11.
When I take a screenshot
fun captureBitmap(frame: CropFrames, response: (bitmap: Bitmap) -> Unit){
delayed(50) {
this.frames = frame
projection = mgr!!.getMediaProjection(resultCode, resultData!!)
val cb: MediaProjection.Callback = object : MediaProjection.Callback() {
override fun onStop() {
vdisplay!!.release() //?
response.invoke(latestBitmap!!)
}
}
vdisplay = projection?.createVirtualDisplay(
NAME,
width,
height,
App.densityDpi,
FLAGS,
imageReader.surface,
null,
null
)
projection?.registerCallback(cb, null)
}
}
onImageAvailable triggered
override fun onImageAvailable(reader: ImageReader) {
try {
val image = imageReader.acquireNextImage()
if (image != null) {
val planes = image.planes
val buffer = planes[0].buffer
val pixelStride = planes[0].pixelStride
val rowStride = planes[0].rowStride
val rowPadding = rowStride - pixelStride * width
val bitmapWidth = width + rowPadding / pixelStride
if (latestBitmap == null || latestBitmap!!.width != bitmapWidth || latestBitmap!!.height != height) {
if (latestBitmap != null) {
latestBitmap!!.recycle()
}
latestBitmap = Bitmap.createBitmap(
bitmapWidth,
height, Bitmap.Config.ARGB_8888
)
}
latestBitmap!!.copyPixelsFromBuffer(buffer)
image.close()
handler.parseFrame(frames!!, latestBitmap!!) {
stopCapture()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Then I release it
fun stopCapture() {
if (projection != null) {
projection!!.stop()
vdisplay!!.release()
projection = null
}
}
This flow can be triggered a lot of times per lifecycle, but each call has increased completion time of execution and it looks like short twitches of UI while taking ascreenshot (no main thread calculations). Probably I don't clear something properly? Any suggestions are appreciated. Thanks!
I am pretty new to Android so I hope I can get some directions here.
I have a 360 camera running on Android 7.0. It includes a SDK to get access to the live stitched images. In this SDK there is a function to set a Surface where the output from the stitched images will be directed to.
This is the function provided by the SDK:
public static void SDK.setSurface(Surface inputSurface)
I want to grab an image from that surface every second.
How do I create the right kind of Surface? And how do I grab images from this Surface?
Any help is highly appreciated!
Since I found the answer I might share it here too.
I have create a class which works like a charm. This is what I used:
class Capture: ImageReader.OnImageAvailableListener{
private var mImageReader: ImageReader? = null
private var mThreadHandler: HandlerThread? = null
fun start() {
if (!init) {
if (mImageReader != null) {
mImageReader?.close()
mImageReader = null
}
if (mThreadHandler != null) {
mThreadHandler?.quitSafely()
mThreadHandler = null
}
mThreadHandler = HandlerThread("prev")
mThreadHandler?.start()
mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 5)
mImageReader?.setOnImageAvailableListener(this, Handler(mThreadHandler?.getLooper()))
SDK.setSurface(if (mImageReader == null) null else mImageReader?.getSurface())
init = true
}
}
override fun onImageAvailable(reader: ImageReader) {
val image = reader.acquireLatestImage()
val height = image.height
val stride = image.planes[0].rowStride / image.planes[0].pixelStride
captureCallback(
stride,
height,
image.timestamp,
image.planes[0].buffer
)
image.close()
}
fun stop() {
if (init) {
if (mImageReader != null) {
mImageReader!!.close()
mImageReader = null
}
if (mThreadHandler != null) {
mThreadHandler!!.quitSafely()
mThreadHandler = null
}
SDK.setSurface(null)
}
init = false
}
fun captureCallback(width: Int, Height: Int, timestamp: Long, data: ByteBuffer) {
// do something with data
}
}
`
I have a nice map with icon markers and a recyclerview setup using Mapbox. It is similar to this:
https://docs.mapbox.com/android/maps/examples/recyclerview-interaction
My problem is that I can not figure out how to set the icons according to my local json property. I have in total around 40 different icons I need to use so I thought the best place to set that would be in my geojson properties. I have the png files for my icons in my drawable-xxhdpi folder. Here is what my local geojson file looks like:
{
"type": "Feature",
"properties": {
"name": "Broadway",
"icon": "route_icon_1",
"hours": "V1",
"phone": "One Star Rating",
"description": "Located at Lower Point Boulders"
},
"geometry": {
"type": "Point",
"coordinates": [
-86.311166,
33.919872
]
}
},
{
"type": "Feature",
"properties": {
"name": "Return of the Jedi",
"icon": "route_icon_2",
"hours": "V7",
"phone": "One Star Rating",
"description": "Located at Lower Point Boulders"
},
"geometry": {
"type": "Point",
"coordinates": [
-86.311174,
33.919883
]
}
},
I'm accessing the geojson file just fine and everything is running perfectly, but I can not seem to set my icon according to the "icon" property in the geojson file. Is this even possible? I apologize for the length of my code. This is my first time asking a question on here and I wasn't sure how much of the code I should post.
Here is my code in my map activity:
class MapActivity : AppCompatActivity(), OnMapReadyCallback, LocationRecyclerViewAdapter.ClickListener,
MapboxMap.OnMapClickListener {
private var featureCollection: FeatureCollection? = null
private var mapboxMap: MapboxMap? = null
private var mapView: MapView? = null
private var locationsRecyclerView: RecyclerView? = null
private var listOfIndividualLocations: ArrayList<IndividualLocation>? = null
private var styleRvAdapter: LocationRecyclerViewAdapter? = null
private var markerSelected = false
private var markerAnimator: ValueAnimator? = null
private var selectedMarkerIcon: Bitmap? = null
private var unselectedMarkerIcon: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Configure the Mapbox access token. Configuration can either be called in your application
// class or in the same activity which contains the mapview.
Mapbox.getInstance(this, getString(R.string.access_token))
// Hide the status bar for the map to fill the entire screen
#Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else {
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
// Inflate the layout with the the MapView. Always inflate this after the Mapbox access token is configured.
setContentView(R.layout.activity_map)
// Create a GeoJSON feature collection from the GeoJSON file in the assets folder.
try {
featureCollectionFromJson
} catch (exception: Exception) {
Log.e("MapActivity", "onCreate: $exception")
Toast.makeText(this, R.string.failure_to_load_file, Toast.LENGTH_LONG).show()
}
// Initialize a list of IndividualLocation objects for future use with recyclerview
listOfIndividualLocations = ArrayList()
// Set up the Mapbox map
mapView = findViewById(R.id.mapView)
mapView?.onCreate(savedInstanceState)
mapView?.getMapAsync(this)
}
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
// Initialize the custom class that handles marker icon creation and map styling based on the selected theme
selectedMarkerIcon = BitmapFactory.decodeResource(
resources,
R.drawable.basic_red_marker
)
unselectedMarkerIcon = BitmapFactory.decodeResource(
resources,
R.drawable.basic_red_marker
)
mapboxMap.setStyle(
Style.MAPBOX_STREETS
)
{ style ->
// Setting the returned mapboxMap object (directly above) equal to the "globally declared" one
this#MapActivity.mapboxMap = mapboxMap
mapboxMap.addOnMapClickListener(this)
val quad = LatLngQuad(
LatLng(33.92307338188975, -86.31129446659145),
LatLng(33.92307338188975, -86.30688718139171),
LatLng(33.91948714730282, -86.30688718139171),
LatLng(33.91948714730282, -86.31129446659145)
)
val quad2 = LatLngQuad(
LatLng(33.921356, -86.307309),
LatLng(33.921356, -86.307156),
LatLng(33.921200, -86.307156),
LatLng(33.921200, -86.307309)
)
// Add an ImageSource to the map
style.addSource(ImageSource(ID_IMAGE_SOURCE, quad, R.drawable.asset14))
style.addSource(ImageSource(ID_IMAGE_SOURCE_VANDALA, quad2, R.drawable.vandala_overlay_sm))
// Create a raster layer and use the imageSource's ID as the layer's data. Then add a RasterLayer to the map.
style.addLayerBelow(
RasterLayer(ID_IMAGE_LAYER, ID_IMAGE_SOURCE),
LAYER_ID
)
style.addLayerAbove(
RasterLayer(ID_IMAGE_LAYER_VANDALA, ID_IMAGE_SOURCE_VANDALA),
ID_IMAGE_LAYER
)
// Set bounds for the map camera so that the user can't pan the map outside of the NYC area
mapboxMap.setLatLngBoundsForCameraTarget(LOCKED_MAP_CAMERA_BOUNDS)
// Set up the SymbolLayer which will show the icons for each route location
initRouteLocationIconSymbolLayer()
// Set up the SymbolLayer which will show the selected route icon
initSelectedRouteSymbolLayer()
// Create a list of features from the feature collection
if (featureCollection != null) {
val featureList: List<Feature> = featureCollection?.features() as List<Feature>
// Retrieve and update the source designated for showing the route location icons
mapboxMap.style?.getSourceAs<GeoJsonSource?>(SOURCE_ID)!!
.setGeoJson(FeatureCollection.fromFeatures(featureList))
for (x in featureList.indices) {
val singleLocation: Feature = featureList[x]
// Get the single location's String properties to place in its map marker
val singleLocationName: String =
singleLocation.getStringProperty("name")
val singleLocationRouteIcon: String =
singleLocation.getStringProperty("icon")
Log.e("tag", singleLocationRouteIcon)
val singleLocationHours: String =
singleLocation.getStringProperty("hours")
val singleLocationDescription: String =
singleLocation.getStringProperty("description")
val singleLocationPhoneNum: String =
singleLocation.getStringProperty("phone")
// Add a boolean property to use for adjusting the icon of the selected route location
singleLocation.addBooleanProperty(PROPERTY_SELECTED, false)
// Get the single location's LatLng coordinates
val singleLocationPosition: Point = singleLocation.geometry() as Point
// Create a new LatLng object with the Position object created above
val singleLocationLatLng = LatLng(
singleLocationPosition.latitude(),
singleLocationPosition.longitude()
)
// Add the location to the Arraylist of locations for later use in the recyclerview
listOfIndividualLocations!!.add(
IndividualLocation(
singleLocationName,
singleLocationRouteIcon,
singleLocationDescription,
singleLocationHours,
singleLocationPhoneNum,
singleLocationLatLng
)
)
}
}
// the Maps SDK's LocationComponent can be used to easily display and customize
// the device location's puck
setUpRecyclerViewOfLocationCards()
Toast.makeText(this#MapActivity, "Click on a card", Toast.LENGTH_SHORT)
.show()
}
}
override fun onMapClick(point: LatLng): Boolean {
Log.e("tag", "onMapClick")
val style = mapboxMap!!.style
if (style != null) {
val selectedMarkerSymbolLayer = style.getLayer("selected-route-location-layer-id") as SymbolLayer?
val pixel = mapboxMap!!.projection.toScreenLocation(point)
val features = mapboxMap!!.queryRenderedFeatures(pixel, "route-location-layer-id")
val selectedFeature = mapboxMap!!.queryRenderedFeatures(
pixel, "selected-route-location-layer-id"
)
if (selectedFeature.size > 0 && markerSelected) {
Log.e(
"tag",
"1 onMapClick: selectedFeature.size > 0 && markerSelected ${selectedFeature.size} $markerSelected"
)
return false
}
if (features.isEmpty()) {
Log.e("tag", "2 onMapClick: features.isEmpty")
if (markerSelected) {
Log.e("tag", "selectMarker = true")
deselectMarker(selectedMarkerSymbolLayer!!)
}
return false
}
val source = style.getSourceAs<GeoJsonSource>(ID_SELECTED_ROUTE_ICON)
source?.setGeoJson(
FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
features[0].geometry()
)
)
)
)
if (markerSelected) {
Log.e("tag", "3 onMapClick: selectMarker")
deselectMarker(selectedMarkerSymbolLayer!!)
}
if (features.size > 0) {
Log.e("tag", "4 onMapClick: features.size > 0")
Log.e("tag", "4 onMapClick: features.size = ${features.size}")
selectMarker(selectedMarkerSymbolLayer!!)
}
}
handleClickIcon(mapboxMap?.projection!!.toScreenLocation(point))
return true
}
private fun handleClickIcon(screenPoint: PointF): Boolean {
Log.e("tag", "handleClickIcon")
val features: List<Feature> = mapboxMap?.queryRenderedFeatures(
screenPoint,
LAYER_ID,
"route-location-layer-id"
) as List<Feature>
return if (features.isNotEmpty()) {
Log.e("tag", "1 handleClickIcon: features.isNotEmpty")
val name: String = features[0].getStringProperty("name")
val featureList: List<Feature> = featureCollection?.features() as List<Feature>
for (i in featureList.indices) {
if (featureList[i].getStringProperty("name") == name) {
Log.e("tag", "(${featureList[i].getStringProperty("name")} == $name)")
val selectedFeaturePoint: Point = featureList[i].geometry() as Point
Log.e("tag", "handleClickIcon: featureSelectStatus = ${featureSelectStatus(i)}")
if (featureSelectStatus(i)) {
Log.e("tag", "${featureSelectStatus(i)}")
Log.e(
"tag", "2 handleClickIcon: featureSelectStatus = ${
featureSelectStatus(
i
)
}"
)
setFeatureSelectState(featureList[i], true)
Log.e(
"tag",
"2 handleClickIcon: setFeatureSelectState(featureList $i: selectedState set to false)"
)
} else {
Log.e(
"tag", "3 handleClickIcon: featureSelectStatus = ${
featureSelectStatus(
i
)
}"
)
setSelected(i)
}
if (selectedFeaturePoint.latitude() != MOCK_DEVICE_LOCATION_LAT_LNG.latitude) {
Log.e(
"tag",
"selectedFeaturePoint.latitude != MOCK_DEVICE_LOCATION_LAT_LNG.latitude"
)
for (x in 0 until featureCollection!!.features()!!.size) {
if (listOfIndividualLocations!![x].getLocation()?.latitude == selectedFeaturePoint.latitude()) {
// Scroll the recyclerview to the selected marker's card. It's "x-1" below because
// the mock device location marker is part of the marker list but doesn't have its own card
// in the actual recyclerview.
locationsRecyclerView!!.smoothScrollToPosition(x)
Log.e("tag", "smoothScrollToPosition")
}
}
}
}
else
{
setFeatureSelectState(featureList[i], false)
Log.e("tag", "4 handleClickIcon: (${featureList[i]}")
}
}
true
}
else
{
false
}
}
private fun selectMarker(iconLayer: SymbolLayer) {
Log.e("tag", "selectMarker: markerSelect set to true")
markerAnimator = ValueAnimator()
markerAnimator!!.setObjectValues(1f, 2f)
markerAnimator!!.duration = 300
markerAnimator!!.addUpdateListener { animator ->
iconLayer.setProperties(
iconSize(animator.animatedValue as Float)
)
}
markerAnimator!!.start()
markerSelected = true
}
private fun deselectMarker(iconLayer: SymbolLayer) {
Log.e("tag", "deselectMarker: markerSelect set to false")
markerAnimator!!.setObjectValues(1.5f, 1f)
markerAnimator!!.duration = 300
markerAnimator!!.addUpdateListener { animator ->
iconLayer.setProperties(
iconSize(animator.animatedValue as Float)
)
}
markerAnimator!!.start()
markerSelected = false
}
/**
* The LocationRecyclerViewAdapter's interface which listens to clicks on each location's card
*
* #param position the clicked card's position/index in the overall list of cards
*/
override fun onItemClick(position: Int) {
Log.e("tag", "itemClick")
// Get the selected individual location via its card's position in the recyclerview of cards
val selectedLocation: IndividualLocation = listOfIndividualLocations!![position]
// Evaluate each Feature's "select state" to appropriately style the location's icon
val featureList: List<Feature> = featureCollection?.features() as List<Feature>
val selectedLocationPoint: Point =
featureCollection!!.features()?.get(position)?.geometry() as Point
for (i in featureList.indices) {
if (featureList[i].getStringProperty("name") == selectedLocation.name) {
Log.e(
"tag",
"(${featureList[i].getStringProperty("name")} == ${selectedLocation.name})"
)
if (featureSelectStatus(i)) {
Log.e("tag", "1 featureSelectStatus = ${featureSelectStatus(i)}")
// setFeatureSelectState(featureList[i], false)
Toast.makeText(
applicationContext,
"Go to ${selectedLocation.name} Details Activity",
Toast.LENGTH_LONG
).show()
Log.e("tag", "Go to ${selectedLocation.name} Details Activity")
} else {
Log.e("tag", "2 itemClick: setSelected $i")
setSelected(i)
}
} else {
setFeatureSelectState(featureList[i], false)
Log.e("tag", "3 itemClick: featureList $i: selectedState false")
}
}
selectedLocation.getLocation()?.let { onCardClick(it) }
// Reposition the map camera target to the selected marker
repositionMapCamera(selectedLocationPoint)
}
private fun onCardClick(point: LatLng): Boolean {
Log.e("tag", "onCardClick")
val style = mapboxMap!!.style
if (style != null) {
val selectedMarkerSymbolLayer = style.getLayer("selected-route-location-layer-id") as SymbolLayer?
val pixel = mapboxMap!!.projection.toScreenLocation(point)
val features = mapboxMap!!.queryRenderedFeatures(pixel, "route-location-layer-id")
val selectedFeature = mapboxMap!!.queryRenderedFeatures(
pixel, "selected-route-location-layer-id"
)
if (selectedFeature.size > 0 && markerSelected) {
Log.e(
"tag",
"1 onCardClick: selectedFeature.size > 0 && markerSelected ${selectedFeature.size} $markerSelected"
)
return false
}
if (features.isEmpty()) {
Log.e("tag", "2 onCardClick: features.isEmpty")
if (markerSelected) {
Log.e("tag", "Do Nothing")
return false
}
else {
Log.e("tag", "markerSelected = $markerSelected")
selectMarker(selectedMarkerSymbolLayer!!)
}
return false
}
val source = style.getSourceAs<GeoJsonSource>(ID_SELECTED_ROUTE_ICON)
source?.setGeoJson(
FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
features[0].geometry()
)
)
)
)
if (markerSelected) {
Log.e("tag", "3 onCardClick: markerSelected")
deselectMarker(selectedMarkerSymbolLayer!!)
}
if (features.size > 0) {
Log.e("tag", "4 onCardClick: features.size > 0")
Log.e("tag", "4 onCardClick: features.size = ${features.size}")
selectMarker(selectedMarkerSymbolLayer!!)
}
}
return true
}
/**
* Adds a SymbolLayer which will show all of the location's icons
*/
private fun initRouteLocationIconSymbolLayer() {
val style: Style? = mapboxMap?.style
if (style != null) {
// Add the icon image to the map
style.addImage(
ID_ROUTE_ICON, unselectedMarkerIcon as Bitmap
)
style.transition = TransitionOptions(0, 0, false)
// Create and add the GeoJsonSource to the map
val routeLocationGeoJsonSource = GeoJsonSource(SOURCE_ID)
style.addSource(routeLocationGeoJsonSource)
// Create and add the route location icon SymbolLayer to the map
val routeLocationSymbolLayer = SymbolLayer(
"route-location-layer-id",
SOURCE_ID
)
routeLocationSymbolLayer.withProperties(
iconImage(ID_ROUTE_ICON),
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f))
)
style.addLayer(routeLocationSymbolLayer)
} else {
Log.d("RouteFinderActivity", "initRouteLocationIconSymbolLayer: Style isn't ready yet.")
throw IllegalStateException("Style isn't ready yet.")
}
}
/**
* Adds a SymbolLayer which will show the selected location's icon
*/
private fun initSelectedRouteSymbolLayer() {
val style: Style? = mapboxMap?.style
if (style != null) {
// Add the icon image to the map
style.addImage(
ID_SELECTED_ROUTE_ICON, selectedMarkerIcon as Bitmap
)
// Create and add the route location icon SymbolLayer to the map
val selectedRouteLocationSymbolLayer = SymbolLayer(
"selected-route-location-layer-id",
SOURCE_ID
)
selectedRouteLocationSymbolLayer.withProperties(
iconImage(ID_SELECTED_ROUTE_ICON),
iconOffset(arrayOf(0f, -9f)),
iconAllowOverlap(true)
)
selectedRouteLocationSymbolLayer.withFilter(eq(get(PROPERTY_SELECTED), literal(true)))
style.addLayer(selectedRouteLocationSymbolLayer)
} else {
Log.d("RouteFinderActivity", "initSelectedRouteSymbolLayer: Style isn't ready yet.")
throw IllegalStateException("Style isn't ready yet.")
}
}
/**
* Checks whether a Feature's boolean "selected" property is true or false
*
* #param index the specific Feature's index position in the FeatureCollection's list of Features.
* #return true if "selected" is true. False if the boolean property is false.
*/
private fun featureSelectStatus(index: Int): Boolean {
return if (featureCollection == null) {
false
} else featureCollection!!.features()?.get(index)?.getBooleanProperty(PROPERTY_SELECTED)!!
}
/**
* Set a feature selected state.
*
* #param index the index of selected feature
*/
private fun setSelected(index: Int) {
Log.e("tag", "setSelected")
val feature: Feature = featureCollection?.features()!![index]
setFeatureSelectState(feature, true)
Log.e(
"tag",
"setSelected: setFeatureSelectedState(feature = ${featureCollection?.features()!![index]}, selectedState set to true"
)
refreshSource()
}
/**
* Selects the state of a feature
*
* #param feature the feature to be selected.
*/
private fun setFeatureSelectState(feature: Feature, selectedState: Boolean) {
feature.properties()?.addProperty(PROPERTY_SELECTED, selectedState)
refreshSource()
}
/**
* Updates the display of data on the map after the FeatureCollection has been modified
*/
private fun refreshSource() {
val source: GeoJsonSource? = mapboxMap?.style?.getSourceAs(SOURCE_ID)
if (source != null && featureCollection != null) {
source.setGeoJson(featureCollection)
}
}
private fun repositionMapCamera(newTarget: Point) {
val newCameraPosition: CameraPosition = CameraPosition.Builder()
.target(LatLng(newTarget.latitude(), newTarget.longitude()))
.zoom(22.0)
.build()
mapboxMap?.animateCamera(
CameraUpdateFactory.newCameraPosition(newCameraPosition),
CAMERA_MOVEMENT_SPEED_IN_MILSECS
)
}
// Use fromJson() method to convert the GeoJSON file into a usable FeatureCollection object
#get:Throws(IOException::class)
private val featureCollectionFromJson: Unit
get() {
try {
// Use fromJson() method to convert the GeoJSON file into a usable FeatureCollection object
featureCollection = "list_of_locations.geojson".loadGeoJsonFromAsset()?.let {
FeatureCollection.fromJson(
it
)
}
} catch (exception: Exception) {
Log.e("MapActivity", "getFeatureCollectionFromJson: $exception")
}
}
private fun String.loadGeoJsonFromAsset(): String? {
Log.e("tag", "loadGeoJsonFromAsset")
return try {
// Load the GeoJSON file from the local asset folder
val `is`: InputStream = assets.open(this)
val size: Int = `is`.available()
val buffer = ByteArray(size)
`is`.read(buffer)
`is`.close()
String(buffer, charset("UTF-8"))
} catch (exception: Exception) {
Log.e("MapActivity", "Exception Loading GeoJSON: $exception")
exception.printStackTrace()
null
}
}
private fun setUpRecyclerViewOfLocationCards() {
Log.e("tag", "seUpRecyclerViewOfLocationCards")
// Initialize the recyclerview of location cards and a custom class for automatic card scrolling
locationsRecyclerView = findViewById(R.id.map_layout_rv)
locationsRecyclerView?.setHasFixedSize(true)
locationsRecyclerView?.layoutManager = LinearLayoutManagerWithSmoothScroller(this)
styleRvAdapter = listOfIndividualLocations?.let {
LocationRecyclerViewAdapter(
it,
applicationContext, this
)
}
locationsRecyclerView?.adapter = styleRvAdapter
val snapHelper: SnapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(locationsRecyclerView)
}
// Add the mapView's lifecycle to the activity's lifecycle methods
public override fun onResume() {
super.onResume()
mapView?.onResume()
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
public override fun onPause() {
super.onPause()
mapView?.onPause()
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView?.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView?.onSaveInstanceState(outState)
}
companion object {
private val LOCKED_MAP_CAMERA_BOUNDS = LatLngBounds.Builder()
.include(LatLng(33.92307338188975, -86.30688718139171))
.include(
LatLng(
33.91948714730282,
-86.31129446659145
)
).build()
private val MOCK_DEVICE_LOCATION_LAT_LNG: LatLng =
LatLng(33.921466408978034, -86.30824978533974)
private const val CAMERA_MOVEMENT_SPEED_IN_MILSECS = 1200
private const val PROPERTY_SELECTED = "selected"
private const val ID_ROUTE_ICON = "route-icon-id"
private const val ID_SELECTED_ROUTE_ICON = "selected-route-icon-id"
private const val LAYER_ID = "LAYER_ID"
private const val ID_IMAGE_SOURCE = "image_source-id"
private const val ID_IMAGE_LAYER = "image_layer-id"
private const val ID_IMAGE_SOURCE_VANDALA = "image_source-id_vandala"
private const val ID_IMAGE_LAYER_VANDALA = "image_layer-id_vandala"
private const val SOURCE_ID = "route-location-source-id"
}
}
Thank you in advance.
You are almost there. You need a bit of code more.
On your function initRouteLocationIconSymbolLayer(), you have the following code:
routeLocationSymbolLayer.withProperties(
iconImage(ID_ROUTE_ICON), // <---- Here is where you have to change
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f))
)
You have only set your layers to have one icon. The MapBoxSDK allows you to play with the properties of the geojson file.
So, here we need to change how the icon image is generated for each type of icon
routeLocationSymbolLayer
.withProperties(
/* iconImage constructor, with some query-like code */
iconImage(
match(
get("icon"), // JSON Key to evaluate
literal("route_icon_1"), // default
stop("route_icon_1", "route_icon_1"), // stop(input,output)
stop("route_icon_2", "route_icon_2")
)
),
/*the remaining properties you declared*/
iconAllowOverlap(true),
iconOffset(arrayOf(0f, -9f)
)
Source: MapBox Android SDK Example
Android has released a new API camerax in recent months. I'm trying to understand how to get auto-focusing for the camera to work.
https://groups.google.com/a/android.com/forum/#!searchin/camerax-developers/auto$20focus|sort:date/camerax-developers/IQ3KZd8iOIY/LIbrRIqEBgAJ
Here is a discussion on the topic but there is almost no specific documentation on it.
https://github.com/android/camera-samples/tree/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic
Here is also the basic camerax app but I couldn't find any file dealing with the auto focusing.
Any tips or points to documentation is helpful. Also I'm fairly new to android so its very possible I'm missing something that makes the above links more useful.
With the current CameraX 1.0.0, you can proceed in this 2 ways:
Auto-focus every X seconds:
previewView.afterMeasured {
val autoFocusPoint = SurfaceOrientedMeteringPointFactory(1f, 1f)
.createPoint(.5f, .5f)
try {
val autoFocusAction = FocusMeteringAction.Builder(
autoFocusPoint,
FocusMeteringAction.FLAG_AF
).apply {
//start auto-focusing after 2 seconds
setAutoCancelDuration(2, TimeUnit.SECONDS)
}.build()
camera.cameraControl.startFocusAndMetering(autoFocusAction)
} catch (e: CameraInfoUnavailableException) {
Log.d("ERROR", "cannot access camera", e)
}
}
Focus on-tap:
previewView.afterMeasured {
previewView.setOnTouchListener { _, event ->
return#setOnTouchListener when (event.action) {
MotionEvent.ACTION_DOWN -> {
true
}
MotionEvent.ACTION_UP -> {
val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
previewView.width.toFloat(), previewView.height.toFloat()
)
val autoFocusPoint = factory.createPoint(event.x, event.y)
try {
camera.cameraControl.startFocusAndMetering(
FocusMeteringAction.Builder(
autoFocusPoint,
FocusMeteringAction.FLAG_AF
).apply {
//focus only when the user tap the preview
disableAutoCancel()
}.build()
)
} catch (e: CameraInfoUnavailableException) {
Log.d("ERROR", "cannot access camera", e)
}
true
}
else -> false // Unhandled event.
}
}
}
afterMeasured extension function is a simple utility: (thanks ch271828n for improving it)
inline fun View.afterMeasured(crossinline block: () -> Unit) {
if (measuredWidth > 0 && measuredHeight > 0) {
block()
} else {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.GlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
block()
}
}
})
}
}
A Camera object can be obtained with
val camera = cameraProvider.bindToLifecycle(
this#Activity, cameraSelector, previewView //this is a PreviewView
)
Just point out, to get the "Tap to focus" working with PreviewView, you need to use
DisplayOrientedMeteringPointFactory. Otherwise you'll get messed up coordinates.
val factory = DisplayOrientedMeteringPointFactory(activity.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat())
For the rest use the MatPag's answer.
There is an issue with some android devices where the camera's aren't auto-focusing with CameraX. The CameraX team is aware of it and are tracking it with an internal ticket and hopefully will have a fix soon.
You can find the doc here about Focus as it was added in "1.0.0-alpha05"
https://developer.android.com/jetpack/androidx/releases/camera#camera2-core-1.0.0-alpha05
Basically you have to set a touch listener on your view and grab the clicked position
private boolean onTouchToFocus(View viewA, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
return focus(event);
break;
default:
// Unhandled event.
return false;
}
return true;
}
And translate this position into point
private boolean focus(MotionEvent event) {
final float x = (event != null) ? event.getX() : getView().getX() + getView().getWidth() / 2f;
final float y = (event != null) ? event.getY() : getView().getY() + getView().getHeight() / 2f;
TextureViewMeteringPointFactory pointFactory = new TextureViewMeteringPointFactory(textureView);
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
float aePointWidth = afPointWidth * 1.5f;
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth, 1.0f);
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth, 1.0f);
try {
CameraX.getCameraControl(lensFacing).startFocusAndMetering(
FocusMeteringAction.Builder.from(afPoint, FocusMeteringAction.MeteringMode.AF_ONLY)
.addPoint(aePoint, FocusMeteringAction.MeteringMode.AE_ONLY)
.build());
} catch (CameraInfoUnavailableException e) {
Log.d(TAG, "cannot access camera", e);
}
return true;
}
With current 1.0.0-rc03 and 1.0.0-alpha22 artifacts
This solution assumes that camera is already setup including bindToLifecycle. After that we need to check whether previewView streamState is STREAMING before trying to focus the camera
previewView.getPreviewStreamState().observe(getActivity(), value -> {
if (value.equals(STREAMING)) {
setUpCameraAutoFocus();
}
});
private void setUpCameraAutoFocus() {
final float x = previewView.getX() + previewView.getWidth() / 2f;
final float y = previewView.getY() + previewView.getHeight() / 2f;
MeteringPointFactory pointFactory = previewView.getMeteringPointFactory();
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
float aePointWidth = afPointWidth * 1.5f;
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
ListenableFuture<FocusMeteringResult> future = cameraControl.startFocusAndMetering(
new FocusMeteringAction.Builder(afPoint,
FocusMeteringAction.FLAG_AF).addPoint(aePoint,
FocusMeteringAction.FLAG_AE).build());
Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
#Override
public void onSuccess(#Nullable FocusMeteringResult result) {
}
#Override
public void onFailure(Throwable t) {
// Throw the unexpected error.
throw new RuntimeException(t);
}
}, CameraXExecutors.directExecutor());
}
The afterMeasured function in highest voted answer has a serious bug: Frequently its callback is never called.
The very simple fix:
inline fun View.afterMeasured(crossinline block: () -> Unit) {
if (measuredWidth > 0 && measuredHeight > 0) {
block()
} else {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
block()
}
}
})
}
}
Explanation: I have observed (in an app in production) that, sometimes the view is already measured and no ui changes so onGlobalLayout will never be called later. Then the afterMeasured's callback will never be called, so the camera is not initialized.
I ran into the same issue and I set up this solution (even if it looks pretty dumb).
val displayMetrics = resources.displayMetrics
val factory = SurfaceOrientedMeteringPointFactory(
displayMetrics.widthPixels.toFloat(),
displayMetrics.heightPixels.toFloat()
)
val point = factory.createPoint(
displayMetrics.widthPixels / 2f,
displayMetrics.heightPixels / 2f
)
val action = FocusMeteringAction
.Builder(point, FocusMeteringAction.FLAG_AF)
.build()
try {
camera = cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageAnalyzer
)
GlobalScope.launch(Dispatchers.Default) {
while (workflowModel.isCameraLive) {
camera?.cameraControl?.startFocusAndMetering(action)?
delay(3000)
}
}
} catch (e: Exception) {
Log.e(mTag, "Use case binding failed", e)
}
Basically, I restart the focusing action every 3s in a while loop.
isCameraLive is a boolean variable I store in my viewModel and I set true when I start the camera and false when I stop it by calling cameraProvider.unbindAll().
I wanna sort some strings that contain numbers but after a sort, it becomes like this ["s1", "s10", "s11", ... ,"s2", "s21", "s22"]. after i search i fount this question with same problem. but in my example, I have mutableList<myModel>, and I must put all string in myModel.title for example into a mutable list and place into under code:
val sortData = reversedData.sortedBy {
//pattern.matcher(it.title).matches()
Collections.sort(it.title, object : Comparator<String> {
override fun compare(o1: String, o2: String): Int {
return extractInt(o1) - extractInt(o2)
}
fun extractInt(s: String): Int {
val num = s.replace("\\D".toRegex(), "")
// return 0 if no digits found
return if (num.isEmpty()) 0 else Integer.parseInt(num)
}
})
}
I have an error in .sortedBy and Collections.sort(it.title), may please help me to fix this.
you can use sortWith instead of sortBy
for example:
class Test(val title:String) {
override fun toString(): String {
return "$title"
}
}
val list = listOf<Test>(Test("s1"), Test("s101"),
Test("s131"), Test("s321"), Test("s23"), Test("s21"), Test("s22"))
val sortData = list.sortedWith( object : Comparator<Test> {
override fun compare(o1: Test, o2: Test): Int {
return extractInt(o1) - extractInt(o2)
}
fun extractInt(s: Test): Int {
val num = s.title.replace("\\D".toRegex(), "")
// return 0 if no digits found
return if (num.isEmpty()) 0 else Integer.parseInt(num)
}
})
will give output:
[s1, s21, s22, s23, s101, s131, s321]
A possible solution based on the data you posted:
sortedBy { "s(\\d+)".toRegex().matchEntire(it)?.groups?.get(1)?.value?.toInt() }
Of course I would move the regex out of the lambda, but it is a more concise answer this way.
A possible solution can be this:
reversedData.toObservable()
.sorted { o1, o2 ->
val pattern = Pattern.compile("\\d+")
val matcher = pattern.matcher(o1.title)
val matcher2 = pattern.matcher(o2.title)
if (matcher.find()) {
matcher2.find()
val o1Num = matcher.group(0).toInt()
val o2Num = matcher2.group(0).toInt()
return#sorted o1Num - o2Num
} else {
return#sorted o1.title?.compareTo(o2.title ?: "") ?: 0
}
}
.toList()
.subscribeBy(
onError = {
it
},
onSuccess = {
reversedData = it
}
)
As you state that you need a MutableList, but don't have one yet, you should use sortedBy or sortedWith (in case you want to work with a comparator) instead and you get just a (new) list out of your current one, e.g.:
val yourMutableSortedList = reversedData.sortedBy {
pattern.find(it)?.value?.toInt() ?: 0
}.toMutableList() // now calling toMutableList only because you said you require one... so why don't just sorting it into a new list and returning a mutable list afterwards?
You may want to take advantage of compareBy (or Javas Comparator.comparing) for sortedWith.
If you just want to sort an existing mutable list use sortWith (or Collections.sort):
reversedData.sortWith(compareBy {
pattern.find(it)?.value?.toInt() ?: 0
})
// or using Java imports:
Collections.sort(reversedData, Compatarator.comparingInt {
pattern.find(it)?.value?.toInt() ?: 0 // what would be the default for non-matching ones?
})
Of course you can also play around with other comparator helpers (e.g. mixing nulls last, or similar), e.g.:
reversedData.sortWith(nullsLast(compareBy {
pattern.find(it)?.value
}))
For the samples above I used the following Regex:
val pattern = """\d+""".toRegex()
I wrote a custom comparator for my JSON sorting. It can be adapted from bare String/Number/Null
fun getComparator(sortBy: String, desc: Boolean = false): Comparator<SearchResource.SearchResult> {
return Comparator { o1, o2 ->
val v1 = getCompValue(o1, sortBy)
val v2 = getCompValue(o2, sortBy)
(if (v1 is Float && v2 is Float) {
v1 - v2
} else if (v1 is String && v2 is String) {
v1.compareTo(v2).toFloat()
} else {
getCompDefault(v1) - getCompDefault(v2)
}).sign.toInt() * (if (desc) -1 else 1)
}
}
private fun getCompValue(o: SearchResource.SearchResult, sortBy: String): Any? {
val sorter = gson.fromJson<JsonObject>(gson.toJson(o))[sortBy]
try {
return sorter.asFloat
} catch (e: ClassCastException) {
try {
return sorter.asString
} catch (e: ClassCastException) {
return null
}
}
}
private fun getCompDefault(v: Any?): Float {
return if (v is Float) v else if (v is String) Float.POSITIVE_INFINITY else Float.NEGATIVE_INFINITY
}