Status Update
Comments
ra...@google.com <ra...@google.com> #2
Can you please describe exactly what method call you're making on ExifInterface
? I would expect this to work for exifInterface.setAttribute(TAG_EXPOSURE_TIME, "1/1600")
but not exifInterface.setAttribute(TAG_EXPOSURE_TIME, "0.000625")
.
ca...@careem.com <ca...@careem.com> #3
```
val sourceExif = ExifInterface(sourceFilePath)
val destinationExif = ExifInterface(destinationFilePath)
exifTagsList
.associateWith { sourceExif.getAttribute(it) }
.forEach { (tag, value) ->
destinationExif.setAttribute(tag, value)
}
destinationExif.saveAttributes()
```
Where exifTagsList contains the tags I need, e.g. ExifInterface.TAG_EXPOSURE_TIME.
I'm not sure if it's okay to simply read all attributes as String and write them as String, but it worked fine for all tags, except this one.
The attribute value I get for `getAttribute(TAG_EXPOSURE_TIME)` is a String `6.25E-4`. This should be parsed correctly as double value of `0,000625`, which is equal to the actual photo exposure time of `1/1600`.
The problem here is IMO the lack of precision - for higher exposure times like 1/100, 1/50, etc. it works fine. It doesn't for smaller values like 1/1600 or 1/3200.
When you run this code, you will get 1/1666 instead of 1/1600, which I suppose is lack of precision, as 1/1666 = 0,0006:
`exifInterface.setAttribute(TAG_EXPOSURE_TIME, "6.25E-4")`
I tried `exifInterface.setAttribute(TAG_EXPOSURE_TIME, "1/1600")` but it didn't work for me. Probably the interface expects a value that it can parse as double. According to ExifInterface documentation:
> TAG_EXPOSURE_TIME
> Type is double.
ra...@google.com <ra...@google.com> #4
Thanks, I think part of the cause of the problem you're seeing is that exifInterface.getAttribute(TAG_EXPOSURE_TIME)
is returning "6.25E-4"
while setAttribute(TAG_EXPOSURE_TIME, value)
is ideally expecting "1/1600"
.
Turns out there is code to handle the case of passing a decimal string to setAttribute
, by converting it to a Rational
representation of { int numerator, int denominator }
, but it (arbitrarily)
Rational(double value) {
this(/* numerator= */ (long) (value * 10000), /* denominator */ 10000);
}
It looks like this code has been present for a long time, seems it was present in ExifInterface
added in 2016
So I think there's an obvious improvement we can make here:
-
Fix the
double
->Rational
conversion to support more precision. Ideally we would first try a denominator of1 / value
(since this would give an exact result in the case of this bug, and probably most other cases since shutter speeds that are less than 1 are usually1/something
), something like:Rational createRationalFromDouble(double value) { double inverse = 1 / value; if (isMathematicalInteger(value)) { return new Rational(1, (long) inverse); } // Not sure what we can do here }
For the
else
case, maybe we could do something clever with continued fractions:https://en.wikipedia.org/wiki/Continued_fraction
It feels like we should also align the format that getAttribute
returns with the format setAttribute
expects, in order to explicitly support the copying use-case you've described. That would mean changing getAttribute
to return "1/1600"
in this case. Unfortunately that is a breaking change for any existing code that is doing Double.parseDouble(exifInterface.getAttribute(TAG_EXPOSURE_TIME))
, so not feasible. If we were to introduce this it would need to be a separate method.
Description
I have created a Worker and a test class running it, but the doWork method is not called.
I have tried to debug the issue and the work is not executed as the constructor of `TestWorkManagerImpl` creates a list of Schedulers with one `null` Scheduler. due to this the Schedulers.schedule method throws a null pointer exception.
Here is the stacktrace from the exception:
androidx.work.impl.Schedulers.schedule(Schedulers.java:91)
androidx.work.impl.utils.EnqueueRunnable.scheduleWorkInBackground(EnqueueRunnable.java:131)
androidx.work.impl.utils.EnqueueRunnable.run(EnqueueRunnable.java:92)
androidx.work.testing.TestWorkManagerImpl$1.executeOnBackgroundThread(TestWorkManagerImpl.java:63)
androidx.work.impl.WorkContinuationImpl.enqueue(WorkContinuationImpl.java:185)
androidx.work.impl.WorkManagerImpl.enqueue(WorkManagerImpl.java:305)
androidx.work.WorkManager.enqueue(WorkManager.java:180)
com.example.carlo.myapplication.testWorker() <--- where WorkManager.getInstance().enqueue(request) is called
I have also created a project with one failing test.
I am using WorkManager 1.0.0-beta01
Please let me know if you need more information