forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 16
[RNA][OTA] Adding image support #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageOTAUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
package com.facebook.react.views.imagehelper; | ||
|
||
import android.content.Context; | ||
import android.graphics.drawable.Drawable; | ||
import android.net.Uri; | ||
import android.util.DisplayMetrics; | ||
import android.view.WindowManager; | ||
|
||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
|
||
import java.io.File; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
public class ImageOTAUtils { | ||
private static final ConcurrentHashMap<String, String> mResourceCacheMap = new ConcurrentHashMap<>(); | ||
private static final String CACHE_DRAWABLE_DIRECTORY_SCHEME = "otas/app/src/main/res"; | ||
private static final String[] RESOURCE_EXTENSIONS = { | ||
"xml", | ||
"png", | ||
"svg", | ||
"jpg" | ||
}; | ||
|
||
private static int densityDpi; | ||
|
||
@Nullable | ||
static Drawable getResourceDrawable(Context context, @Nullable String name) { | ||
if (name == null || name.isEmpty()) { | ||
return null; | ||
} | ||
|
||
name = sanitizeResourceDrawableId(name); | ||
|
||
// Checks to see if we have an ota version of the file, otherwise default to normal behavior. | ||
File otaFile = getDrawableFileFromCache(context, name); | ||
if (otaFile != null) { | ||
return Drawable.createFromPath(otaFile.getAbsolutePath()); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
@Nullable | ||
static Uri getResourceUri(Context context, @Nullable String name) { | ||
if (name == null || name.isEmpty()) { | ||
return Uri.EMPTY; | ||
} | ||
|
||
name = sanitizeResourceDrawableId(name); | ||
|
||
// Checks to see if we have an ota version of the file, otherwise default to normal behavior. | ||
File otaFile = ImageOTAUtils.getDrawableFileFromCache(context, name); | ||
if (otaFile != null) { | ||
return Uri.fromFile(otaFile); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Checks the cache to see if there is a drawable file downloaded via OTA. | ||
*/ | ||
@Nullable | ||
private static File getDrawableFileFromCache(Context context, @Nullable String fileName) { | ||
if (fileName == null || fileName.isEmpty()) { | ||
return null; | ||
} | ||
|
||
String cacheMapFileName = mResourceCacheMap.get(fileName); | ||
|
||
// Check the cache to see if we've already looked up the file before. | ||
if (cacheMapFileName != null) { | ||
return new File(cacheMapFileName); | ||
} | ||
|
||
File file = null; | ||
int densityDpi = getDensityDpi(context); | ||
PhoneDensity[] phoneDensities = PhoneDensity.values(); | ||
|
||
// We start from the medium dpi and go up. | ||
for (PhoneDensity phoneDensity : phoneDensities) { | ||
String drawableFileParent = String.format("drawable-%s", phoneDensity.fileParentSuffix); | ||
String mipMapFileParent = String.format("mipmap-%s", phoneDensity.fileParentSuffix); | ||
|
||
String[] parentFileNames = { drawableFileParent, mipMapFileParent }; | ||
|
||
File resourceFile = checkFiles(context, parentFileNames, fileName); | ||
|
||
if (resourceFile != null) { | ||
file = resourceFile; | ||
} | ||
|
||
// If we've found a file at our current dpi level, return it. | ||
// Otherwise continue looking up the chain. | ||
if (densityDpi <= phoneDensity.density) { | ||
if (file != null) { | ||
mResourceCacheMap.put(fileName, file.getAbsolutePath()); | ||
return file; | ||
} | ||
} | ||
} | ||
|
||
// As a last resort, check the drawable/raw folders. | ||
String[] parentFileNames = { "drawable", "raw" }; | ||
file = checkFiles(context, parentFileNames, fileName); | ||
|
||
if (file != null) { | ||
mResourceCacheMap.put(fileName, file.getAbsolutePath()); | ||
} | ||
|
||
return file; | ||
} | ||
|
||
/** | ||
* Given a list of files, check if any of them exist. | ||
* Checks multiple extension types. | ||
*/ | ||
private static File checkFiles(Context context, String[] parentFileNames, String fileName) { | ||
for(String parentFileName : parentFileNames) { | ||
for (String extension : RESOURCE_EXTENSIONS) { | ||
File file = getFile(context, parentFileName, fileName, extension); | ||
if (file.exists()) { | ||
return file; | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Returns a file object with the correct directory extensions. | ||
*/ | ||
private static File getFile( | ||
Context context, | ||
String parentFileName, | ||
String fileName, | ||
String extension | ||
) { | ||
String fullDrawableFileName = String.format( | ||
"%s/%s/%s.%s", | ||
CACHE_DRAWABLE_DIRECTORY_SCHEME, | ||
parentFileName, | ||
fileName, | ||
extension | ||
); | ||
|
||
return new File(context.getCacheDir(), fullDrawableFileName); | ||
} | ||
|
||
/** | ||
* Returns the density dpi for the device. | ||
*/ | ||
private static int getDensityDpi(Context context) { | ||
// Cache this so we only have to do this once. | ||
if (densityDpi == 0) { | ||
DisplayMetrics metrics = new DisplayMetrics(); | ||
|
||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | ||
windowManager.getDefaultDisplay().getMetrics(metrics); | ||
|
||
densityDpi = metrics.densityDpi; | ||
} | ||
|
||
return densityDpi; | ||
} | ||
|
||
private static String sanitizeResourceDrawableId(@NonNull String name) { | ||
return name.toLowerCase().replace("-", "_"); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/PhoneDensity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.facebook.react.views.imagehelper; | ||
|
||
import android.util.DisplayMetrics; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
enum PhoneDensity { | ||
Medium(DisplayMetrics.DENSITY_MEDIUM, "mdpi"), | ||
High(DisplayMetrics.DENSITY_HIGH, "hdpi"), | ||
XHigh(DisplayMetrics.DENSITY_XHIGH, "xhdpi"), | ||
XXHigh(DisplayMetrics.DENSITY_XXHIGH, "xxhdpi"), | ||
XXXHigh(DisplayMetrics.DENSITY_XXXHIGH, "xxxhdpi"); | ||
|
||
int density; | ||
|
||
@NonNull | ||
String fileParentSuffix; | ||
|
||
PhoneDensity( | ||
int density, | ||
@NonNull String fileParentSuffix | ||
) { | ||
this.density = density; | ||
this.fileParentSuffix = fileParentSuffix; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.