Google Calendar API V3 android - Get all day events for freeBusyRequest - android

I'm trying to get the Free busy data of other people that are within my Google organization using the google-api-services-calendar:v3 for Android (using Kotlin). I'm getting the times just fine for events with a set duration. But all day events don't show up on the list. Documentation on this is almost nowhere to be found and the stuff that I find on developers.google.com contains code that was deprecated in 2013...
// ...
val busyTimesList = mutableListOf<AgendaPlotter.TimeSpan>()
SessionService.sharedInstance.getGoogleAccount(activity)
observeOn(Schedulers.io())
.subscribe {
mCredential!!.selectedAccount = it.account
val request = FreeBusyRequest()
val durationCal = Calendar.getInstance()
durationCal.time = startDay.time
Calendars.startOfDay(durationCal)
request.timeMin = DateTime(durationCal.time)
durationCal.add(Calendar.DATE, 1)
request.timeMax = DateTime(durationCal.time)
val requestItems = listOf(FreeBusyRequestItem().setId("email#from.colleague"))
request.items = requestItems
request.timeZone = TimeZone.getDefault().id
val busyTimes: FreeBusyResponse
try {
val query = mService!!.freebusy().query(request)
// Use partial GET to retrieve only needed fields.
query.fields = "calendars"
busyTimes = query.execute()
busyTimes.calendars.forEach {
it.toPair().second.busy.forEach { timeSpan ->
val busyTime = AgendaPlotter.TimeSpan()
busyTime.fromTime.timeInMillis = timeSpan.start.value
busyTime.toTime.timeInMillis = timeSpan.end.value
busyTimesList.add(busyTime)
}
}
emitter.onNext(busyTimesList)
} catch (e: IOException) {
e.printStackTrace()
// ...
}
}
// ...
So my question, how do I also obtain the whole day events?

After some searching I noticed that there is actually nothing wrong with the API. It's a setting for a whole day event to be busy of free.
By default this is set to free, which makes it not show as a busy time, which makes sense. This goes unnoticed by a lot of people and they will be "free" on that day.

Related

Movie is deprecated now

I'm looking for the alternate of Movie like to get the duration of GIF. I tried in imageDecoder but I can't able to get the duration.
//Deprecated
val movie = Movie.decodeStream(`is`)
val duration = movie.duration()
Movie probably still works even though it's deprecated. But if you're not going to go on to use that Movie instance to play the GIF, that's a bad way of getting the duration because it will have to load the entire thing when all you really need to find the duration is in the meta data at the beginning of the file.
You could use the Metadata Extractor library to do this.
Since it's reading from a file, it is blocking and should be done in the background. Here's an example using a suspend function to accomplish that.
/** Returns duration in ms of the GIF of the stream, 0 if it has no duration,
* or null if it could not be read. */
suspend fun InputStream.readGifDurationOrNull(): Int? = withContext(Dispatchers.IO) {
try {
val metadata = ImageMetadataReader.readMetadata(this#readGifDurationOrNull)
val gifControlDirectories = metadata.getDirectoriesOfType(GifControlDirectory::class.java)
if (gifControlDirectories.size <= 1) {
return#withContext 0
}
gifControlDirectories.sumOf {
it.getInt(GifControlDirectory.TAG_DELAY) * 10 // Gif uses 10ms units
}
} catch (e: Exception) {
Log.e("readGifDurationOrNull", "Could not read metadata from input", e)
null
}
}
Credit to this answer for how to get the appropriate duration info from the metadata.

how to process the text returned by firebase ML text recognition

I am using the firebase ML vision to read RC details from the image.
implementation "com.google.android.gms:play-services-mlkit-text-recognition:17.0.1"
private fun Activity.recognizeText(imageSting: String) {
var image: InputImage? = null
try {
image = InputImage.fromFilePath(getContext(), Uri.fromFile(File(imageSting)))
} catch (e: IOException) {
e.printStackTrace()
}
// [START get_detector_default]
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
// [END get_detector_default]
// [START run_detector]
val result = recognizer.process(image)
.addOnSuccessListener { visionText ->
processTextBlock(visionText)
}
.addOnFailureListener { e ->
Log.d("test", e.localizedMessage);
}
}
it works fine and text is returned successfully.
list of data is returened with cornerPoints and boundingBox how map data correclty ..
ex- Reg. No. with actual value
Government of 1Tamil Nadu
Certificate of Registration
O
Reg. No.
Date of Reg.
TNO6P7094
30-06-2015
Reg. Valid Till
29-06-2030
Chassis No.
ME4JF504FFT459059
Engine No.
JF50ET 2460128
Owner
02
Sr. No.
Owner Name
NIKIL KUMAR ROY
Fuel Used
PETROL
Son/Daughter/Wife of
MANIK ROY
Address
NO 456/HPTRAILWAY
COLONY 4TH STREET
AYANAVARAM Chennai TN 600023
Well if you want reg.no. you could go several ways:
Filter out from all that text by regex
val regex = Regex("[A-Z]{3}[0-9]{1}[A-Z]{1}[0-9]{4}")
text.textBlocks.map {
it.lines.filter {
regex.matches(it.text)
}
}
Filter out from area of picture (probably then with regex additionally)
// this is camera preview
previewCamera.getGlobalVisibleRect(previewCameraRect)
// here you would calculate the position on the picture, where you would expect needed thing. Do not forget to calculate on change of rotation
Rect(left.roundToInt(), top.roundToInt(), right.roundToInt(), bottom.roundToInt())
text.textBlocks.filter { textBlock ->
textBlock.boundingBox?.let { rect.contains(it) } ?: false
}
Combination of those two. You probably can make more elaborate algorithm that would combine position and text matching. So something like
// Pseudocode
foreach (textBlocks as textBlock) {
if (textBlock.text.equals("Reg.No")) {
position = textBlock.boundingBox
continue
}
if (isNear(textBlock.boundingBox, position) && regex.matches(textBlock.text)) {
// this is part of text you are looking for
}
}

Android Auto: How to return a large number of children on loadChildren() of MediaBrowserService?

I am currently trying to implement a MediaBrowserService to build a media app for Android Auto.
I followed the official Android Auto documentation (https://developer.android.com/training/cars/media#onLoadChildren) to implement theonLoadChildren function.
Following is a code snippet that I tried to show the content on the Android Auto screen:
override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
...
if (parentId == NODE_LIBRARY_ALBUMS) {
val items = mutableListOf<MediaBrowserCompat.MediaItem>()
val albumList = LibraryManager.getAlbumList()
for (it in albumList) {
val descriptionBuilder = MediaDescriptionCompat.Builder()
.setTitle(it.albumName)
items.add(MediaBrowserCompat.MediaItem(descriptionBuilder.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
result.sendResult(items)
}
...
}
This works pretty well, when the number of items is small enough.
However, when the number of items is large (e.g., about 5,000 items), the following error appears:
E/JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 1339384)
I found that several other media apps (e.g., Samsung Music) that support Android Auto can show a large number of items.
Is there any way to return a large number of items on the onLoadChildren function, or is there any other way to solve this issue?
Thanks!
Probably you have to split the large data into a small pieces. For example, you have a list of 5000 items. So inside you MediaBrowserService in onLoadChildren do something like this
public fun onLoadChildren(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
if (MEDIA_ID_ROOT == parentId || itemsList.size > 100) {
fillMediaBrowsableResult(parentId, result);
}
else {
fillMediaPlayableResult(parentId, result);
}
}
//Split the large number of content to a parts
private fun fillMediaBrowsableResult(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
// Detect count of parts
val partsCount = itemsList.size / 100
if(itemsList.size % 100 > 0){
partsCount ++
}
val mediaItems = mutableListOf<MediaBrowserCompat.MediaItem>()
//Create parts in a loop
for(i in 0 until partsCount){
val mediaDescription = MediaDescriptionCompat.Builder()
.setMediaId(i) // This is your next parent in onLoadChildren when you click on it in AA
.setTitle("Part ${i + 1}")
.build();
val mediaItem =. MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
mediaItems.add(mediaItem)
}
result.sendResult(mediaItems)
}
private fun fillMediaPlayableResult(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>){
val intParent = parentId.toInt()
val startPosition = intParent * 100 // where to start for this part
val stopPosition = (intParent + 1) * 100 // where to finish for this part
if(stopPosition > itemsList.size){
stopPosition = itemsList.size
}
val mediaItems = mutableListOf<MediaBrowserCompat.MediaItem>()
for(i in startPosition..stopPosition){
//Generate playable content for this part
val item = itemsList[i]
val mediaDescription = MediaDescriptionCompat.Builder()
.setMediaId(item.id)
.setTitle(item.albumTitle)
.build();
val mediaItem =. MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
mediaItems.add(mediaItem)
}
result.sendResult(mediaItems)
}
I didn't check this code, but I think the idea is clear.
If you look into Android SDK document:
Note: Android Auto and Android Automotive OS have strict limits on how
many media items they can display in each level of the menu. These
limits minimize distractions for drivers and help them operate your
app with voice commands. ...
So, I think the best approach is to limit # of media items in UX design. For what you saw from the other apps working with lots of media items, they might used Jetpack Media 2 for pagination.

How do I get the JSON result from Amadeus API (Kotlin Android)

I am trying to use the Amadeus Offers Search API with the following code:
when (val flightOffers = amadeus.shopping.flightOffersSearch.get(
originLocationCode = "MDZ",
destinationLocationCode = "MAD",
departureDate = LocalDate.parse("2020-11-11").toString(),
adults = 2,
max = 1
)) {
is ApiResult.Success -> {
if (flightOffers.succeeded) {
println("RESULT SUCCEEDED")
println(flightOffers.data)
}
else
{
println("RESULT DIDN'T SUCCEEDED")
}
}
is ApiResult.Error -> {
println("RESULT ERROR")
}
}
And if I compile that the logcat output is as follows:
I/System.out: RESULT SUCCEEDED
Which makes me think that flightOffers.data is empty.
However if I try this code:
val flightOffers = amadeus.shopping.flightOffersSearch.get(
originLocationCode = "MDZ",
destinationLocationCode = "MAD",
departureDate = LocalDate.parse("2020-11-11").toString(),
adults = 2,
max = 1
)
println("AMADEUS: $flightOffers")
I get the following output:
I/System.out: AMADEUS: Success(meta=Meta(count=1, links={self=https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MDZ&destinationLocationCode=MAD&departureDate=2020-11-11&adults=2&max=1}), data=[FlightOfferSearch(type=flight-offer, id=1, source=GDS, instantTicketingRequired=false, nonHomogeneous=false, oneWay=false, lastTicketingDate=2020-05-03, numberOfBookableSeats=7, itineraries=[Itinerary(duration=PT18H, segments=[SearchSegment(departure=AirportInfo(iataCode=MDZ, terminal=null, at=2020-11-11T07:10:00), arrival=AirportInfo(iataCode=AEP, terminal=null, at=2020-11-11T08:45:00), carrierCode=AR, number=1403, aircraft=Aircraft(code=738), duration=PT1H35M, id=1, numberOfStops=0, blacklistedInEU=false, co2Emissions=null), SearchSegment(departure=AirportInfo(iataCode=EZE, terminal=A, at=2020-11-11T13:25:00), arrival=AirportInfo(iataCode=MAD, terminal=1, at=2020-11-12T05:10:00), carrierCode=UX, number=42, aircraft=Aircraft(code=789), duration=PT11H45M, id=2, numberOfStops=0, blacklistedInEU=false, co2Emissions=null)])], price=SearchPrice(currency=EUR, total=1151.26, base=510.0, fees=[Fee(amount=0.0, type=SUPPLIER), Fee(amount=0.0, type=TICKETING)], grandTotal=1151.26), pricingOptions=PricingOptions(includedCheckedBagsOnly=true, fareType=[PUBLISHED], corporateCodes=null, refundableFare=false, noRestrictionFare=false, noPenaltyFare=false), validatingAirlineCodes=[UX], travelerPricings=[TravelerPricing(travelerId=1, fareOption=STANDARD, travelerType=ADULT, price=SearchPrice(currency=EUR, total=575.63, base=255.0, fees=null, grandTotal=0.0), fareDetailsBySegment=[FareDetailsBySegment(segmentId=1, cabin=ECONOMY, fareBasis=ZYYOPO, segmentClass=Q, includedCheckedBags=IncludedCheckedBags(weight=0, weightUnit=null)), FareDetailsBySegment(segmentId=2, cabin=ECONOMY, fareBasis=ZYYOPO, segmentClass=Z, includedCheckedBags=IncludedCheckedBags(weight=0, weightUnit=null))]), TravelerPricing(travelerId=2, fareOption=STANDARD, travelerType=ADULT, price=SearchPrice(currency=EUR, total=575.63, base=255.0, fees=null, grandTotal=0.0), fareDetailsBySegment=[FareDetailsBySegment(segmentId=1, cabin=ECONOMY, fareBasis=ZYYOPO, segmentClass=Q, includedCheckedBags=IncludedCheckedBags(weight=0, weightUnit=null)), FareDetailsBySegment(segmentId=2, cabin=ECONOMY, fareBasis=ZYYOPO, segmentClass=Z, includedCheckedBags=IncludedCheckedBags(weight=0, weightUnit=null))])])], dictionaries={locations={MAD={cityCode=MAD, countryCode=ES}, EZE={cityCode=BUE, countryCode=AR}, MDZ={cityCode=MDZ, countryCode=AR}, AEP={cityCode=BUE, countryCode=AR}}, aircraft={789=BOEING 787-9, 738=BOEING 737-800}, currencies={EUR=EURO}, carriers={AR=AEROLINEAS ARGENTINAS, UX=AIR EUROPA}})
Which means that the API is returning a JSON but then I can't use flightOffers with gson to pass this data to a DataClass because flightOffers is a ApiResult> and I don't know how to use that. According to their library docs it should be done like I did it in the first try.
I appreciate all the help and advice I can get. This is my first Android App.
Nice to see that we have a new Android developer in the community !
So first, in Android you should avoid using println, instead you should use Log.d/e/w/i, this method will print your result in android logcat.
For what I see you successfully setup your project and where able to make query from the sdk.
In the android sdk, every get() will give you a correct data object and not just JSON. You don't have to take care of parsing the answer. The thing you have in your flightOffers.data is in fact a List<FlightOfferSearch> that you can use right away !

How can I trim a video from Uri, including files that `mp4parser` library can handle, but using Android's framework instead?

Background
Over the past few days, I've worked on making a customizable, more updated version of a library for video trimming, here (based on this library)
The problem
While for the most part, I've succeeded making it customizable and even converted all files into Kotlin, it had a major issue with the trimming itself.
It assumes the input is always a File, so if the user chooses an item from the apps chooser that returns a Uri, it crashes. The reason for this is not just the UI itself, but also because a library that it uses for trimming (mp4parser) assumes an input of only File (or filepath) and not a Uri (wrote about it here). I tried multiple ways to let it get a Uri instead, but failed. Also wrote about it here.
That's why I used a solution that I've found on StackOverflow (here)for the trimming itself. The good thing about it is that it's quiet short and uses just Android's framework itself. However, it seems that for some video files, it always fails to trim them. As an example of such files, there is one on the original library repository, here (issue reported here).
Looking at the exception, this is what I got:
E: Unsupported mime 'audio/ac3'
E: FATAL EXCEPTION: pool-1-thread-1
Process: life.knowledge4.videocroppersample, PID: 26274
java.lang.IllegalStateException: Failed to add the track to the muxer
at android.media.MediaMuxer.nativeAddTrack(Native Method)
at android.media.MediaMuxer.addTrack(MediaMuxer.java:626)
at life.knowledge4.videotrimmer.utils.TrimVideoUtils.genVideoUsingMuxer(TrimVideoUtils.kt:77)
at life.knowledge4.videotrimmer.utils.TrimVideoUtils.genVideoUsingMp4Parser(TrimVideoUtils.kt:144)
at life.knowledge4.videotrimmer.utils.TrimVideoUtils.startTrim(TrimVideoUtils.kt:47)
at life.knowledge4.videotrimmer.BaseVideoTrimmerView$initiateTrimming$1.execute(BaseVideoTrimmerView.kt:220)
at life.knowledge4.videotrimmer.utils.BackgroundExecutor$Task.run(BackgroundExecutor.java:210)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
What I've found
Reported about the issue here. I don't think it will get an answer, as the library hasn't updated in years...
Looking at the exception, I tried to also trim without sound. This works, but it's not a good thing, because we want to trim normally.
Thinking that this code might be based on someone else's code, I tried to find the original one. I've found that it is based on some old Google code on its gallery app, here, in a class called "VideoUtils.java" in package of "Gallery3d". Sadly, I don't see any new version for it. Latest one that I see is of Gingerbread, here.
The code that I've made out of it looks as such:
object TrimVideoUtils {
private const val DEFAULT_BUFFER_SIZE = 1024 * 1024
#JvmStatic
#WorkerThread
fun startTrim(context: Context, src: Uri, dst: File, startMs: Long, endMs: Long, callback: VideoTrimmingListener) {
dst.parentFile.mkdirs()
//Log.d(TAG, "Generated file path " + filePath);
val succeeded = genVideoUsingMuxer(context, src, dst.absolutePath, startMs, endMs, true, true)
Handler(Looper.getMainLooper()).post { callback.onFinishedTrimming(if (succeeded) Uri.parse(dst.toString()) else null) }
}
//https://stackoverflow.com/a/44653626/878126 https://android.googlesource.com/platform/packages/apps/Gallery2/+/634248d/src/com/android/gallery3d/app/VideoUtils.java
#JvmStatic
#WorkerThread
private fun genVideoUsingMuxer(context: Context, uri: Uri, dstPath: String, startMs: Long, endMs: Long, useAudio: Boolean, useVideo: Boolean): Boolean {
// Set up MediaExtractor to read from the source.
val extractor = MediaExtractor()
// val isRawResId=uri.scheme == "android.resource" && uri.host == context.packageName && !uri.pathSegments.isNullOrEmpty())
val fileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")!!.fileDescriptor
extractor.setDataSource(fileDescriptor)
val trackCount = extractor.trackCount
// Set up MediaMuxer for the destination.
val muxer = MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// Set up the tracks and retrieve the max buffer size for selected tracks.
val indexMap = SparseIntArray(trackCount)
var bufferSize = -1
try {
for (i in 0 until trackCount) {
val format = extractor.getTrackFormat(i)
val mime = format.getString(MediaFormat.KEY_MIME)
var selectCurrentTrack = false
if (mime.startsWith("audio/") && useAudio) {
selectCurrentTrack = true
} else if (mime.startsWith("video/") && useVideo) {
selectCurrentTrack = true
}
if (selectCurrentTrack) {
extractor.selectTrack(i)
val dstIndex = muxer.addTrack(format)
indexMap.put(i, dstIndex)
if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
val newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
bufferSize = if (newSize > bufferSize) newSize else bufferSize
}
}
}
if (bufferSize < 0)
bufferSize = DEFAULT_BUFFER_SIZE
// Set up the orientation and starting time for extractor.
val retrieverSrc = MediaMetadataRetriever()
retrieverSrc.setDataSource(fileDescriptor)
val degreesString = retrieverSrc.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
if (degreesString != null) {
val degrees = Integer.parseInt(degreesString)
if (degrees >= 0)
muxer.setOrientationHint(degrees)
}
if (startMs > 0)
extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
// Copy the samples from MediaExtractor to MediaMuxer. We will loop
// for copying each sample and stop when we get to the end of the source
// file or exceed the end time of the trimming.
val offset = 0
var trackIndex: Int
val dstBuf = ByteBuffer.allocate(bufferSize)
val bufferInfo = MediaCodec.BufferInfo()
// try {
muxer.start()
while (true) {
bufferInfo.offset = offset
bufferInfo.size = extractor.readSampleData(dstBuf, offset)
if (bufferInfo.size < 0) {
//InstabugSDKLogger.d(TAG, "Saw input EOS.");
bufferInfo.size = 0
break
} else {
bufferInfo.presentationTimeUs = extractor.sampleTime
if (endMs > 0 && bufferInfo.presentationTimeUs > endMs * 1000) {
//InstabugSDKLogger.d(TAG, "The current sample is over the trim end time.");
break
} else {
bufferInfo.flags = extractor.sampleFlags
trackIndex = extractor.sampleTrackIndex
muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
bufferInfo)
extractor.advance()
}
}
}
muxer.stop()
return true
// } catch (e: IllegalStateException) {
// Swallow the exception due to malformed source.
//InstabugSDKLogger.w(TAG, "The source video file is malformed");
} catch (e: Exception) {
e.printStackTrace()
} finally {
muxer.release()
}
return false
}
}
The exception is thrown on val dstIndex = muxer.addTrack(format) . For now, I've wrapped it in try-catch, to avoid a real crash.
I tried to search for newer versions of this code (assuming that it got fixed later), but failed.
Searching on the Internet and here, I've found only one similar question, here, but it's not the same at all.
The questions
Is it possible to use Android's framework to trim such problematic files? Maybe there is a newer version of the trimming of the videos code? I'm interested of course only for the pure implementation of video trimming, like the function I wrote above, of "genVideoUsingMuxer" .
As a temporary solution, is it possible to detect problematic input videos, so that I won't let the user start to trim them, as I know they will fail?
Is there maybe another alternative to both of those, that have a permissive license and doesn't bloat the app? For mp4parser, I wrote a separate question, here.
Why does it occur?
audio/ac3 is an unsupported mime type.
MediaMuxer.addTrack() (native) calls MPEG4Writer.addSource(), which prints this log message before returning an error.
EDIT
My aim was not to provide an answer to each of your sub-questions, but to give you some insight into the fundamental problem. The library you have chosen relies on the Android's MediaMuxer component. For whatever reason, the MediaMuxer developers did not add support for this particular audio format. We know this because the software prints out an explicit message to that effect, then immediately throws the IllegalStateException mentioned in your question.
Because the issue only involves a particular audio format, when you provide a video-only input, everything works fine.
To fix the problem, you can either alter the library to provide for the missing functionality, or find a new library that better suits your needs. sannies/mp4parser may be one such alternative, although it has different limitations (if I recall correctly, it requires all media to be in RAM during the mastering process). I do not know if it supports ac3 explicitly, but it should provide a framework to which you can add support for arbitrary mime types.
I would encourage you to wait for a more complete answer. There may be far better ways to do what you are trying to do. But it is apparent that the library you are using simply does not support all possible mime types.

Categories

Resources