I was wondering whether it was possible to do such a thing. I know that one would need to modify some of the existing code to pull this off but I was wondering if anyone had any direction on where to look and how to do this.
I am placing a few custom tiles on a specific area on the map as a replacement for OSM tiles providers but need them to be stored in the /assets/ folder. Any ideas?
I use the nexts classes to do that.
import java.io.InputStream;
import org.osmdroid.ResourceProxy.string;
import org.osmdroid.tileprovider.util.StreamUtils;
import android.content.res.AssetManager;
import android.graphics.drawable.Drawable;
public class AssetsTileSource extends CustomBitmapTileSourceBase {
private final AssetManager mAssetManager;
public AssetsTileSource(final AssetManager assetManager, final String aName, final string aResourceId,
final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
final String aImageFilenameEnding) {
super(aName, aResourceId, aZoomMinLevel, aZoomMaxLevel, aTileSizePixels, aImageFilenameEnding);
mAssetManager = assetManager;
}
#Override
public Drawable getDrawable(final String aFilePath) {
InputStream inputStream = null;
try {
inputStream = mAssetManager.open(aFilePath);
if (inputStream != null) {
final Drawable drawable = getDrawable(inputStream);
return drawable;
}
} catch (final Throwable e) {
// Tile does not exist in assets folder.
// Ignore silently
} finally {
if (inputStream != null) {
StreamUtils.closeStream(inputStream);
}
}
return null;
}
}
MapTileFileAssetsProvider.java
public class MapTileFileAssetsProvider extends MapTileModuleProviderBase {
protected ITileSource mTileSource;
public MapTileFileAssetsProvider(final ITileSource pTileSource) {
super(OpenStreetMapTileProviderConstants.NUMBER_OF_TILE_FILESYSTEM_THREADS, OpenStreetMapTileProviderConstants.TILE_FILESYSTEM_MAXIMUM_QUEUE_SIZE);
mTileSource = pTileSource;
}
#Override
public boolean getUsesDataConnection() {
return false;
}
#Override
protected String getName() {
return "Assets Folder Provider";
}
#Override
protected String getThreadGroupName() {
return "assetsfolder";
}
#Override
protected Runnable getTileLoader() {
return new TileLoader();
}
#Override
public int getMinimumZoomLevel() {
return mTileSource != null ? mTileSource.getMinimumZoomLevel() : MAXIMUM_ZOOMLEVEL;
}
#Override
public int getMaximumZoomLevel() {
return mTileSource != null ? mTileSource.getMaximumZoomLevel() : MINIMUM_ZOOMLEVEL;
}
#Override
public void setTileSource(final ITileSource pTileSource) {
mTileSource = pTileSource;
}
private class TileLoader extends MapTileModuleProviderBase.TileLoader {
#Override
public Drawable loadTile(final MapTileRequestState pState) throws CantContinueException {
if (mTileSource == null) {
return null;
}
final MapTile pTile = pState.getMapTile();
String path = mTileSource.getTileRelativeFilenameString(pTile);
Drawable drawable;
try {
drawable = mTileSource.getDrawable(path);
} catch (final LowMemoryException e) {
// low memory so empty the queue
throw new CantContinueException(e);
}
return drawable;
}
}
}
And
import java.io.File;
import java.io.InputStream;
import java.util.Random;
import org.osmdroid.ResourceProxy;
import org.osmdroid.ResourceProxy.string;
import org.osmdroid.tileprovider.ExpirableBitmapDrawable;
import org.osmdroid.tileprovider.MapTile;
import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
public abstract class CustomBitmapTileSourceBase implements ITileSource,
OpenStreetMapTileProviderConstants {
private static final Logger logger = LoggerFactory.getLogger(CustomBitmapTileSourceBase.class);
private static int globalOrdinal = 0;
private final int mMinimumZoomLevel;
private final int mMaximumZoomLevel;
private final int mOrdinal;
protected final String mName;
protected final String mImageFilenameEnding;
protected final Random random = new Random();
private final int mTileSizePixels;
private final string mResourceId;
public CustomBitmapTileSourceBase(final String aName, final string aResourceId,
final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
final String aImageFilenameEnding) {
mResourceId = aResourceId;
mOrdinal = globalOrdinal++;
mName = aName;
mMinimumZoomLevel = aZoomMinLevel;
mMaximumZoomLevel = aZoomMaxLevel;
mTileSizePixels = aTileSizePixels;
mImageFilenameEnding = aImageFilenameEnding;
}
#Override
public int ordinal() {
return mOrdinal;
}
#Override
public String name() {
return mName;
}
public String pathBase() {
return mName;
}
public String imageFilenameEnding() {
return mImageFilenameEnding;
}
#Override
public int getMinimumZoomLevel() {
return mMinimumZoomLevel;
}
#Override
public int getMaximumZoomLevel() {
return mMaximumZoomLevel;
}
#Override
public int getTileSizePixels() {
return mTileSizePixels;
}
#Override
public String localizedName(final ResourceProxy proxy) {
return proxy.getString(mResourceId);
}
#Override
public Drawable getDrawable(final String aFilePath) {
try {
// default implementation will load the file as a bitmap and create
// a BitmapDrawable from it
final Bitmap bitmap = BitmapFactory.decodeFile(aFilePath);
if (bitmap != null) {
return new ExpirableBitmapDrawable(bitmap);
} else {
// if we couldn't load it then it's invalid - delete it
try {
new File(aFilePath).delete();
} catch (final Throwable e) {
logger.error("Error deleting invalid file: " + aFilePath, e);
}
}
} catch (final OutOfMemoryError e) {
logger.error("OutOfMemoryError loading bitmap: " + aFilePath);
System.gc();
}
return null;
}
#Override
public String getTileRelativeFilenameString(final MapTile tile) {
final StringBuilder sb = new StringBuilder();
sb.append(pathBase());
sb.append('/');
sb.append(tile.getX());
sb.append('_');
sb.append(tile.getY());
sb.append('_');
sb.append(tile.getZoomLevel());
sb.append(imageFilenameEnding());
return sb.toString();
}
#Override
public Drawable getDrawable(final InputStream aFileInputStream) {
try {
// default implementation will load the file as a bitmap and create
// a BitmapDrawable from it
final Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream);
if (bitmap != null) {
return new ExpirableBitmapDrawable(bitmap);
}
System.gc();
} catch (final OutOfMemoryError e) {
logger.error("OutOfMemoryError loading bitmap");
System.gc();
//throw new LowMemoryException(e);
}
return null;
}
public final class LowMemoryException extends Exception {
private static final long serialVersionUID = 146526524087765134L;
public LowMemoryException(final String pDetailMessage) {
super(pDetailMessage);
}
public LowMemoryException(final Throwable pThrowable) {
super(pThrowable);
}
}
}
Modify method getTileRelativeFilenameString() to get yout tiles (i use the next format: x_y_zoom.png)
Example:
mapView = new MapView(getApplicationContext(), 256);
mapView.setClickable(true);
mapView.setTag("Mapa");
mapView.setTileSource(TileSourceFactory.MAPNIK);
mapView.setMultiTouchControls(true);
mapView.setUseDataConnection(true);
MapTileModuleProviderBase moduleProvider =
new MapTileFileAssetsProvider(ASSETS_TILE_SOURCE);
SimpleRegisterReceiver simpleReceiver =
new SimpleRegisterReceiver(getApplicationContext());
MapTileProviderArray tileProviderArray =
new MapTileProviderArray(ASSETS_TILE_SOURCE, simpleReceiver,
new MapTileModuleProviderBase[] { moduleProvider });
TilesOverlay tilesOverlay =
new TilesOverlay(tileProviderArray, getApplicationContext());
mapView.getOverlays().add(tilesOverlay);
Instead to read directly from assets I copy/deploy the maptiles zipped (following osmdroid map tiles directory structure format) into osmdroid maptiles directory and then declare 3 tile providers, archive, cache and online provider.
public class MapTileProviderAssets extends MapTileProviderArray
implements IMapTileProviderCallback {
private static final String LOG_TAG = "MapTileProviderAssets";
private static final String ASSETS_MAP_DIRECTORY = "map";
private static final String SDCARD_PATH = Environment.getExternalStorageDirectory().getPath();
private static final String OSMDROID_MAP_FILE_SOURCE_DIRECTORY = "osmdroid";
private static final String OSMDROID_MAP_FILE_SOURCE_DIRECTORY_PATH =
SDCARD_PATH + "/" + OSMDROID_MAP_FILE_SOURCE_DIRECTORY;
public MapTileProviderAssets(final Context pContext) {
this(pContext, TileSourceFactory.DEFAULT_TILE_SOURCE);
}
public MapTileProviderAssets(final Context pContext, final ITileSource pTileSource) {
this(pContext, new SimpleRegisterReceiver(pContext),
new NetworkAvailabliltyCheck(pContext), pTileSource);
}
public MapTileProviderAssets(final Context pContext, final IRegisterReceiver pRegisterReceiver,
final INetworkAvailablityCheck aNetworkAvailablityCheck,
final ITileSource pTileSource) {
super(pTileSource, pRegisterReceiver);
final TileWriter tileWriter = new TileWriter();
// copy assets delivered in apk into osmdroid map source dir
// load zip archive first, then cache, then online
final List<String> zipArchivesRelativePathInAssets =
listArchives(pContext.getAssets(), ASSETS_MAP_DIRECTORY);
for (final String zipFileRelativePathInAssets : zipArchivesRelativePathInAssets) {
final String copiedFilePath = copyAssetFile(
pContext.getAssets(), zipFileRelativePathInAssets,
OSMDROID_MAP_FILE_SOURCE_DIRECTORY);
Log.d(LOG_TAG, String.format(
"Archive zip file copied into map source directory %s", copiedFilePath));
}
// list zip files in map archive directory
final Set<String> setZipFileArchivesPath = new HashSet<String>();
FileTools.listFiles(setZipFileArchivesPath, new File(
OSMDROID_MAP_FILE_SOURCE_DIRECTORY_PATH), ".zip", true);
final Set<IArchiveFile> setZipFileArchives = new HashSet<IArchiveFile>();
for (final String zipFileArchivesPath : setZipFileArchivesPath) {
final File zipfile = new File(zipFileArchivesPath);
final IArchiveFile archiveFile = ArchiveFileFactory.getArchiveFile(zipfile);
if (archiveFile != null) {
setZipFileArchives.add(archiveFile);
}
setZipFileArchives.add(archiveFile);
Log.d(LOG_TAG, String.format(
"Archive zip file %s added to map source ", zipFileArchivesPath));
}
final MapTileFileArchiveProvider archiveProvider;
Log.d(LOG_TAG, String.format(
"%s archive zip files will be used as source", setZipFileArchives.size()));
if (setZipFileArchives.size() > 0) {
final IArchiveFile[] pArchives =
setZipFileArchives.toArray(new IArchiveFile[setZipFileArchives.size()]);
archiveProvider = new MapTileFileArchiveProvider(
pRegisterReceiver, pTileSource, pArchives);
} else {
archiveProvider = new MapTileFileArchiveProvider(
pRegisterReceiver, pTileSource);
}
mTileProviderList.add(archiveProvider);
// cache
final MapTileFilesystemProvider fileSystemProvider =
new MapTileFilesystemProvider(pRegisterReceiver, pTileSource);
mTileProviderList.add(fileSystemProvider);
// online tiles
final MapTileDownloader downloaderProvider =
new MapTileDownloader(pTileSource, tileWriter, aNetworkAvailablityCheck);
mTileProviderList.add(downloaderProvider);
}
public static List<String> listArchives(final AssetManager assetManager,
final String subDirectory) {
final List<String> listArchives = new ArrayList<String>();
try {
final String[] lstFiles = assetManager.list(subDirectory);
if (lstFiles != null && lstFiles.length > 0) {
for (final String file : lstFiles) {
if (isZip(file)) {
listArchives.add(subDirectory + "/" + file);
}
// filter files (xxxxx.xxx format) and parse only directories,
// with out this all files are parsed and
// the process is VERY slow
// WARNNING: we could have directories with dot for versioning
else if (isDirectory(file)) {// (file.lastIndexOf(".") != (file.length() - 4)) {
listArchives(assetManager, subDirectory + "/" + file);
}
}
}
} catch (final IOException e) {
Log.w(LOG_TAG, String.format("List error: can't list %s, exception %s",
subDirectory, Log.getStackTraceString(e)));
} catch (final Exception e) {
Log.w(LOG_TAG, String.format("List error: can't list %s, exception %s",
subDirectory, Log.getStackTraceString(e)));
}
return listArchives;
}
private static boolean isZip(final String file) {
return file.endsWith(".zip");
}
private static boolean isDirectory(final String file) {
return file.lastIndexOf(".") != (file.length() - 4);
}
private static String copyAssetFile(final AssetManager assetManager,
final String assetRelativePath,
final String destinationDirectoryOnSdcard) {
InputStream in = null;
OutputStream out = null;
final String newfilePath = SDCARD_PATH + "/" +
destinationDirectoryOnSdcard + "/" + assetRelativePath;
final File newFile = new File(newfilePath);
// copy file only if it doesn't exist yet
if (!newFile.exists()) {
Log.d(LOG_TAG, String.format(
"Copy %s map archive in assets into %s", assetRelativePath, newfilePath));
try {
final File directory = newFile.getParentFile();
if (!directory.exists()) {
if (directory.mkdirs()) {
// Log.d(LOG_TAG, "Directory created: " + directory.getAbsolutePath());
}
}
in = assetManager.open(assetRelativePath);
out = new FileOutputStream(newfilePath);
copyFile(in, out);
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch (final Exception e) {
Log.e(LOG_TAG, "Exception during copyAssetFile: " + Log.getStackTraceString(e));
}
}
return newfilePath;
}
private static void copyFile(final InputStream in, final OutputStream out) throws IOException {
final byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
}
Related
hey guys I am creating a WhatsApp sticker app that fetch stickers from Firebase cloud Firestore and display it into recycler view. Each recycler view contains list of stickers from each sticker pack. On clicking recycler view I am storing the images from firebase into my app external directory but I dont know how to expose these stickers that are stored inside my app directory to WhatsApp. I tried creating Json file and stored it inside the same directory where I have stored my stickers but it was of no use. I am stuck on what to do next. I am sharing the code that I have tried. Any help is highly appreciated.
MainActivity.java:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference stickerPacksRef = db.collection("stickers_pack");
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
stickerPacksRef.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
List<StickerPacks> stickerPacks = new ArrayList<>();
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
String name = documentSnapshot.getString("name");
List<String> stickerUrls = (List<String>) documentSnapshot.get("stickers");
StickerPacks stickerPack = new StickerPacks(name, stickerUrls);
stickerPacks.add(stickerPack);
}
StickerPacksAdapter adapter = new StickerPacksAdapter(MainActivity.this, stickerPacks);
recyclerView.setAdapter(adapter);
}
});
}
}
StickerContentProvider.java:
public class StickerContentProvider extends ContentProvider {
public static final String STICKER_PACK_IDENTIFIER_IN_QUERY = "sticker_pack_identifier";
public static final String STICKER_PACK_NAME_IN_QUERY = "sticker_pack_name";
public static final String STICKER_PACK_PUBLISHER_IN_QUERY = "sticker_pack_publisher";
public static final String STICKER_PACK_ICON_IN_QUERY = "sticker_pack_icon";
public static final String ANDROID_APP_DOWNLOAD_LINK_IN_QUERY = "android_play_store_link";
public static final String IOS_APP_DOWNLOAD_LINK_IN_QUERY = "ios_app_download_link";
public static final String PUBLISHER_EMAIL = "sticker_pack_publisher_email";
public static final String PUBLISHER_WEBSITE = "sticker_pack_publisher_website";
public static final String PRIVACY_POLICY_WEBSITE = "sticker_pack_privacy_policy_website";
public static final String LICENSE_AGREENMENT_WEBSITE = "sticker_pack_license_agreement_website";
public static final String IMAGE_DATA_VERSION = "image_data_version";
public static final String AVOID_CACHE = "whatsapp_will_not_cache_stickers";
public static final String ANIMATED_STICKER_PACK = "animated_sticker_pack";
public static final String STICKER_FILE_NAME_IN_QUERY = "sticker_file_name";
public static final String STICKER_FILE_EMOJI_IN_QUERY = "sticker_emoji";
private static final String CONTENT_FILE_NAME = "contents.json";
public static final Uri AUTHORITY_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.METADATA).build();
/**
* Do not change the values in the UriMatcher because otherwise, WhatsApp will not be able to fetch the stickers from the ContentProvider.
*/
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final String METADATA = "metadata";
private static final int METADATA_CODE = 1;
private static final int METADATA_CODE_FOR_SINGLE_PACK = 2;
static final String STICKERS = "stickers";
private static final int STICKERS_CODE = 3;
static final String STICKERS_ASSET = "stickers_asset";
private static final int STICKERS_ASSET_CODE = 4;
private static final int STICKER_PACK_TRAY_ICON_CODE = 5;
private List<StickerPack> stickerPackList;
#Override
public boolean onCreate() {
final String authority = BuildConfig.CONTENT_PROVIDER_AUTHORITY;
if (!authority.startsWith(Objects.requireNonNull(getContext()).getPackageName())) {
throw new IllegalStateException("your authority (" + authority + ") for the content provider should start with your package name: " + getContext().getPackageName());
}
MATCHER.addURI(authority, METADATA, METADATA_CODE);
MATCHER.addURI(authority, METADATA + "/*", METADATA_CODE_FOR_SINGLE_PACK);
MATCHER.addURI(authority, STICKERS + "/*", STICKERS_CODE);
File contentsFile=new File(getContext().getExternalFilesDir(""),"Danish/"+CONTENT_FILE_NAME);
if(contentsFile.exists()) {
for (StickerPack stickerPack : getStickerPackList()) {
MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + stickerPack.trayImageFile, STICKER_PACK_TRAY_ICON_CODE);
for (Sticker sticker : stickerPack.getStickers()) {
MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + sticker.imageFileName, STICKERS_ASSET_CODE);
}
}
}
return true;
}
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
final int code = MATCHER.match(uri);
if (code == METADATA_CODE) {
return getPackForAllStickerPacks(uri);
} else if (code == METADATA_CODE_FOR_SINGLE_PACK) {
return getCursorForSingleStickerPack(uri);
} else if (code == STICKERS_CODE) {
return getStickersForAStickerPack(uri);
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
#Nullable
#Override
public AssetFileDescriptor openAssetFile(#NonNull Uri uri, #NonNull String mode) {
final int matchCode = MATCHER.match(uri);
if (matchCode == STICKERS_ASSET_CODE || matchCode == STICKER_PACK_TRAY_ICON_CODE) {
try {
return getImageAsset(uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
return null;
}
#Override
public String getType(#NonNull Uri uri) {
final int matchCode = MATCHER.match(uri);
switch (matchCode) {
case METADATA_CODE:
return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA;
case METADATA_CODE_FOR_SINGLE_PACK:
return "vnd.android.cursor.item/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA;
case STICKERS_CODE:
return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + STICKERS;
case STICKERS_ASSET_CODE:
return "image/webp";
case STICKER_PACK_TRAY_ICON_CODE:
return "image/png";
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
private synchronized void readContentFile(#NonNull Context context) {
File contentsFile=new File(context.getExternalFilesDir(""),"Danish/"+CONTENT_FILE_NAME);
if(contentsFile.exists()) {
try (FileInputStream contentsInputStream = new FileInputStream(contentsFile)) {
stickerPackList = ContentFileParser.parseStickerPacks(contentsInputStream);
} catch (IOException | IllegalStateException e) {
throw new RuntimeException(CONTENT_FILE_NAME + " file has some issues: " + e.getMessage(), e);
}
}
}
private List<StickerPack> getStickerPackList() {
if (stickerPackList == null) {
readContentFile(Objects.requireNonNull(getContext()));
}
return stickerPackList;
}
private Cursor getPackForAllStickerPacks(#NonNull Uri uri) {
return getStickerPackInfo(uri, getStickerPackList());
}
private Cursor getCursorForSingleStickerPack(#NonNull Uri uri) {
final String identifier = uri.getLastPathSegment();
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
return getStickerPackInfo(uri, Collections.singletonList(stickerPack));
}
}
return getStickerPackInfo(uri, new ArrayList<>());
}
#NonNull
private Cursor getStickerPackInfo(#NonNull Uri uri, #NonNull List<StickerPack> stickerPackList) {
MatrixCursor cursor = new MatrixCursor(
new String[]{
STICKER_PACK_IDENTIFIER_IN_QUERY,
STICKER_PACK_NAME_IN_QUERY,
STICKER_PACK_PUBLISHER_IN_QUERY,
STICKER_PACK_ICON_IN_QUERY,
ANDROID_APP_DOWNLOAD_LINK_IN_QUERY,
IOS_APP_DOWNLOAD_LINK_IN_QUERY,
PUBLISHER_EMAIL,
PUBLISHER_WEBSITE,
PRIVACY_POLICY_WEBSITE,
LICENSE_AGREENMENT_WEBSITE,
IMAGE_DATA_VERSION,
AVOID_CACHE,
ANIMATED_STICKER_PACK,
});
for (StickerPack stickerPack : stickerPackList) {
MatrixCursor.RowBuilder builder = cursor.newRow();
builder.add(stickerPack.identifier);
builder.add(stickerPack.name);
builder.add(stickerPack.publisher);
builder.add(stickerPack.trayImageFile);
builder.add(stickerPack.androidPlayStoreLink);
builder.add(stickerPack.iosAppStoreLink);
builder.add(stickerPack.publisherEmail);
builder.add(stickerPack.publisherWebsite);
builder.add(stickerPack.privacyPolicyWebsite);
builder.add(stickerPack.licenseAgreementWebsite);
builder.add(stickerPack.imageDataVersion);
builder.add(stickerPack.avoidCache ? 1 : 0);
builder.add(stickerPack.animatedStickerPack ? 1 : 0);
}
cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri);
return cursor;
}
#NonNull
private Cursor getStickersForAStickerPack(#NonNull Uri uri) {
final String identifier = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(new String[]{STICKER_FILE_NAME_IN_QUERY, STICKER_FILE_EMOJI_IN_QUERY});
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
for (Sticker sticker : stickerPack.getStickers()) {
cursor.addRow(new Object[]{sticker.imageFileName, TextUtils.join(",", sticker.emojis)});
}
}
}
cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri);
return cursor;
}
private AssetFileDescriptor getImageAsset(Uri uri) throws IllegalArgumentException, FileNotFoundException {
AssetManager am = Objects.requireNonNull(getContext()).getAssets();
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() != 3) {
throw new IllegalArgumentException("path segments should be 3, uri is: " + uri);
}
String fileName = pathSegments.get(pathSegments.size() - 1);
final String identifier = pathSegments.get(pathSegments.size() - 2);
if (TextUtils.isEmpty(identifier)) {
throw new IllegalArgumentException("identifier is empty, uri: " + uri);
}
if (TextUtils.isEmpty(fileName)) {
throw new IllegalArgumentException("file name is empty, uri: " + uri);
}
//making sure the file that is trying to be fetched is in the list of stickers.
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
if (fileName.equals(stickerPack.trayImageFile)) {
return fetchFile(uri, am, fileName, identifier);
} else {
for (Sticker sticker : stickerPack.getStickers()) {
if (fileName.equals(sticker.imageFileName)) {
return fetchFile(uri, am, fileName, identifier);
}
}
}
}
}
return null;
}
private AssetFileDescriptor fetchFile(#NonNull Uri uri, #NonNull AssetManager am, #NonNull String fileName, #NonNull String identifier) throws FileNotFoundException {
final File cacheFile = getContext().getExternalFilesDir("");
final File file = new File(cacheFile, "Danish/"+fileName);
return new AssetFileDescriptor(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY), 0, AssetFileDescriptor.UNKNOWN_LENGTH);
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Not supported");
}
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException("Not supported");
}
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException("Not supported");
}
}
StickerPacksAdapter.java:
class StickerPacksAdapter extends RecyclerView.Adapter<StickerPacksAdapter.StickerPackViewHolder> {
private List<StickerPacks> stickerPacks;
private Context context;
public StickerPacksAdapter(Context context, List<StickerPacks> stickerPacks) {
this.context = context;
this.stickerPacks = stickerPacks;
}
#NonNull
#Override
public StickerPackViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_sticker_pack, parent, false);
return new StickerPackViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull StickerPackViewHolder holder, int position) {
StickerPacks stickerPacks = this.stickerPacks.get(position);
holder.nameTextView.setText(stickerPacks.getName());
List<String> stickerUrls = stickerPacks.getStickerUrls();
for (int i = 0; i < stickerUrls.size(); i++) {
String stickerUrl = stickerUrls.get(i);
ImageView imageView = holder.stickerImageViews[i];
if(imageView!=null)
Glide.with(holder.itemView.getContext()).load(stickerUrl).into(imageView);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
List<String> stickerUrls = stickerPacks.getStickerUrls();
String stickerPackName = stickerPacks.getName();
File directory = new File(context.getExternalFilesDir(""), stickerPackName);
if (!directory.exists()) {
directory.mkdirs();
}
for (int i = 0; i < stickerUrls.size(); i++) {
String stickerUrl = stickerUrls.get(i);
String fileName = "sticker" + i + ".webp"; // you can choose your own file name
File file = new File(directory, fileName);
FirebaseStorage.getInstance().getReferenceFromUrl(stickerUrl).getDownloadUrl()
.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
String url = uri.toString();
new AsyncTask<Void, Void, Void>() {
#SuppressLint("StaticFieldLeak")
#Override
protected Void doInBackground(Void... voids) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
try (InputStream inputStream = response.body().byteStream()) {
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
outputStream.close();
}
JSONObject json = new JSONObject();
try {
json.put("android_play_store_link", "");
json.put("ios_app_store_link", "");
}catch (JSONException e)
{
e.printStackTrace();
}
JSONArray stickerPacks = new JSONArray();
JSONObject stickerPack = new JSONObject();
try {
stickerPack.put("identifier", stickerPackName.toLowerCase());
stickerPack.put("name", stickerPackName);
stickerPack.put("publisher", stickerPackName);
stickerPack.put("tray_image_file", "sticker0.webp");
stickerPack.put("image_data_version", "1");
stickerPack.put("avoid_cache", false);
stickerPack.put("publisher_email", "");
stickerPack.put("publisher_website", "");
stickerPack.put("privacy_policy_website", "");
stickerPack.put("license_agreement_website", "");
stickerPack.put("animated_sticker_pack", false);
} catch (JSONException e) {
e.printStackTrace();
}
JSONArray stickersArray = new JSONArray();
int i=0;
for (String stickerUrl : stickerUrls) {
JSONObject sticker = new JSONObject();
try {
sticker.put("image_file", "sticker"+i+".webp");
sticker.put("emojis", new JSONArray().put("🥰").put("😘").put("❤️"));
stickersArray.put(sticker);
i++;
} catch (JSONException e) {
e.printStackTrace();
}
}
try {
stickerPack.put("stickers", stickersArray);
stickerPacks.put(stickerPack);
json.put("sticker_packs", stickerPacks);
File file = new File(context.getExternalFilesDir("")+"/"+stickerPackName, "contents.json");
try (FileOutputStream outputStream = new FileOutputStream(file)) {
Log.d("TAG", "entered: ");
outputStream.write(json.toString().getBytes());
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (JSONException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
});
}
Intent intent = new Intent();
intent.setAction("com.whatsapp.intent.action.ENABLE_STICKER_PACK");
intent.putExtra("sticker_pack_id", stickerPackName);
intent.putExtra("sticker_pack_authority", BuildConfig.CONTENT_PROVIDER_AUTHORITY);
intent.putExtra("sticker_pack_name", stickerPackName);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace(); }
}
});
}
#Override
public int getItemCount() {
return stickerPacks.size();
}
public class StickerPackViewHolder extends RecyclerView.ViewHolder {
private TextView nameTextView;
private ImageView[] stickerImageViews;
public StickerPackViewHolder(#NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.sticker_pack_name_text_view);
stickerImageViews = new ImageView[30];
for (int i = 0; i < 4; i++) {
stickerImageViews[i] = itemView.findViewById(getStickerImageViewId(i));
}
}
private int getStickerImageViewId(int index) {
switch (index) {
case 0:
return R.id.sticker1;
case 1:
return R.id.sticker2;
case 2:
return R.id.sticker3;
case 3:
return R.id.sticker4;
// and so on, up to 30
default:
throw new IllegalArgumentException("Invalid sticker image view index: " + index);
}
}
}
}
I'm working on this project where I generated CSV files from contact list, and now I'm supposed to package all the files as a single zip archive using RxJava, so I'm trying to get the method to create an archive invoked using onComplete method. But the application is crashing with this error:
Attempt to invoke direct method 'boolean appjoe.wordpress.com.testdemo.Tab2$FileHelper.zip(java.lang.String, java.lang.String)' on a null object reference
This is my code:
path: /storage/emulated/0/Android/data/com.wordpress.appjoe/csv
File location = new File(Environment.getExternalStorageDirectory(), "Android/data/com.wordpress.appjoe/csv/");
File fileLocation;
FileOutputStream dest;
String path;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_tab2, container, false);
mbutton = v.findViewById(R.id.extractContact);
mbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Permission has already been granted
Observer observer = new Observer() {
#Override
public void onSubscribe(Disposable d) {
mCursor = getCursor();
fCursor = getCursor();
location.mkdirs();
path = location.getAbsolutePath();
try {
dest = new FileOutputStream(path);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
#Override
public void onNext(Object o) {
contactData = o.toString();
fCursor.moveToPosition(count);
fileLocation = new File(path, getName(fCursor)+".csv");
try {
FileOutputStream fileOut = new FileOutputStream(fileLocation);
fileOut.write(contactData.getBytes());
fileOut.flush();
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
mCursor.close();
fCursor.close();
// Creating archive once the CSV files are generated
if (fileHelper.zip(path, location.getParent())) {
Toast.makeText(getContext(), "Zip successful", Toast.LENGTH_SHORT).show();
}
Log.d("fileLocation", "location: " + location.getParent());
Log.d("fileLocation", "path: " + path);
Log.d("Observer_contact", "Completed");
}
};
io.reactivex.Observable.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
try {
for (count = 0; count < mCursor.getCount(); count++) {
emitter.onNext(loadContacts(count));
}
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}
}).subscribeOn(Schedulers.io())
.distinct()
.subscribeWith(observer);
}
}
});
// Inflate the layout for this fragment
return v;
}
RxJava implementation to fetch contacts:
public String loadContacts(int i) {
StringBuilder mBuilder = new StringBuilder();
ContentResolver mContentResolver = getActivity().getContentResolver();
mCursor.moveToPosition(i);
if (mCursor.getCount() > 0 ) {
String id = getID(mCursor);
String name = getName(mCursor);
int hasPhoneNumber = hasNumber(mCursor);
if (hasPhoneNumber > 0) {
mBuilder.append("\"").append(name).append("\"");
Cursor cursor = mContentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "= ?",
new String[]{id}, null);
assert cursor != null;
while (cursor.moveToNext()) {
String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
.replaceAll("\\s", "");
// if number is not existing in the list, then add number to the string
if (!(mBuilder.toString().contains(phoneNumber))) {
mBuilder.append(", ").append(phoneNumber);
}
}
cursor.close();
}
}
return mBuilder.toString();
}
Methods to get necessary information:
private Cursor getCursor() {
ContentResolver mContentResolver = getActivity().getContentResolver();
return mContentResolver.query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
private String getID(Cursor cursor) {
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
return id;
}
private String getName(Cursor cursor) {
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
return name;
}
private int hasNumber(Cursor cursor) {
return Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)));
}
Class to generate archive
// Custom class to help with zipping of generated CSVs
private class FileHelper {
private static final int BUFFER_SIZE = 2048;
private String TAG = FileHelper.class.getName();
private String parentPath = "";
private String destinationFileName = "Contacts_CSV.zip";
private boolean zip (String sourcePath, String destinationPath) {
new File(destinationPath).mkdirs();
FileOutputStream fileOutputStream;
ZipOutputStream zipOutputStream = null;
try {
if (!destinationPath.endsWith("/")) {
destinationPath = destinationPath + "/";
}
String destination = destinationPath + destinationFileName;
File file = new File(destination);
if (!file.exists()) {
file.createNewFile();
}
fileOutputStream = new FileOutputStream(file);
zipOutputStream = new ZipOutputStream(new BufferedOutputStream(fileOutputStream));
parentPath = new File(sourcePath).getParent() + "/";
zipFile(zipOutputStream, sourcePath);
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG,e.getMessage());
return false;
} finally {
if (zipOutputStream!=null)
try {
zipOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void zipFile (ZipOutputStream zipOutputStream, String sourcePath) throws IOException {
java.io.File files = new java.io.File(sourcePath);
java.io.File[] fileList = files.listFiles();
String entryPath="";
BufferedInputStream input;
for (java.io.File file : fileList) {
if (file.isDirectory()) {
} else {
byte data[] = new byte[BUFFER_SIZE];
FileInputStream fileInputStream = new FileInputStream(file.getPath());
input = new BufferedInputStream(fileInputStream, BUFFER_SIZE);
entryPath = file.getAbsolutePath().replace(parentPath, "");
ZipEntry entry = new ZipEntry(entryPath);
zipOutputStream.putNextEntry(entry);
int count;
while ((count = input.read(data, 0, BUFFER_SIZE)) != -1) {
zipOutputStream.write(data, 0, count);
}
input.close();
}
}
}
}
I solved this problem by removing the class FileHelper and making both the methods inside FileHelper private methods within the main class. That way, calling the methods from onComplete() didn't cause the program to crash, and it was successfully generating the zip file.
I wrote my own camera app. This app writes exif information in the jpg files. It works well but I have some problems with the exifInferface class, e.g., I get the following errors when re-read the JPG file:
Warning Invalid EXIF text encoding
Warning Invalid size (8589934590) for IFD0 tag 0x8827
Warning Bad IFD1 directory
I know that my IFD0 pointer at the exif-Information is broken. It may be that while writing the exif information the pointer is broken?
However, I've Googled and found nothing
I use this class to read an write the exif-informations:
public class ExifHelper {
private String aperature = null;
private String exposureTime = null;
private String flash = null;
private String focalLength = null;
private String gpsAltitude = null;
private String gpsAltitudeRef = null;
private String gpsDateStamp = null;
private String gpsLatitude = null;
private String gpsLatitudeRef = null;
private String gpsLongitude = null;
private String gpsLongitudeRef = null;
private String gpsProcessingMethod = null;
private String gpsTimestamp = null;
private String iso = null;
private String make = null;
private String model = null;
private String imageLength = null;
private String imageWidth = null;
private String orientation = null;
private String whiteBalance = null;
private String exifVersion = null;
private String time = null;
private ExifInterface inFile = null;
private ExifInterface outFile = null;
final static String TAG = "ExifHelper";
/**
* The file before it is compressed
*
* #param filePath
* #throws IOException
*/
public void createInFile(String filePath) throws IOException {
this.inFile = new ExifInterface(filePath);
}
/**
* The file after it has been compressed
*
* #param filePath
* #throws IOException
*/
public void createOutFile(String filePath) throws IOException {
this.outFile = new ExifInterface(filePath);
}
/**
* Reads all the EXIF data from the input file.
*/
public void readExifData() {
this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE);
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
this.imageLength = inFile.getAttribute(ExifInterface.TAG_IMAGE_LENGTH);
this.imageWidth = inFile.getAttribute(ExifInterface.TAG_IMAGE_WIDTH);
this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
this.exifVersion = inFile.getAttribute("ExifVersion");
}
/**
* Writes the previously stored EXIF data to the output file.
* #param pictureDate
* #param orientationValues
* #param accelValues
*
* #throws IOException
*/
public void writeExifData(String pictureDate) throws IOException {
// Don't try to write to a null file
if (this.outFile == null) {
return;
}
if (this.aperature != null) {
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature);
}
if (this.exposureTime != null) {
this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
}
if (this.flash != null) {
this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
}
if (this.focalLength != null) {
this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
}
if (this.gpsAltitude != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
}
if (this.gpsAltitudeRef != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
}
if (this.gpsDateStamp != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
}
if (this.gpsLatitude != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
}
if (this.gpsLatitudeRef != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
}
if (this.gpsLongitude != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
}
if (this.gpsLongitudeRef != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
}
if (this.gpsProcessingMethod != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
}
if (this.gpsTimestamp != null) {
this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
}
if (this.iso != null) {
this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
}
if (this.make != null) {
this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
}
if (this.model != null) {
this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
}
if (this.orientation != null) {
this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
}
if (this.whiteBalance != null) {
this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
}
if (this.exifVersion != null) {
this.outFile.setAttribute("ExifVersion", this.exifVersion);
}
this.outFile.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, this.imageLength);
this.outFile.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, this.imageWidth);
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, pictureDate);
String mString = exifRandomZahlen();
this.outFile.setAttribute("UserComment", mString);
this.outFile.saveAttributes();
}
I also write 2 images form the takepicture-method. One the original there is no problem and the second with modify exif informations. There is the problem with the exifinterface I think by writing it back to the JPG.
I used the tool DumpImage to view the exif informations. This tool is from the metaworking group (www.metadataworkinggroup.org)
So I have a big question, How I can fix this broken exif data? For example the IDF0 pointer
somebody know or have the same problem?
I get, for example, the Tag TAG_DATETIME two times in my exif information
this is the class for saveing the photo:
public class Photo extends Activity implements PictureCallback {
public interface OnPictureTakenListener {
void pictureTaken(File pictureFile, File pictureFilePatched, String exifDateString);
}
private final Context context;
private OnPictureTakenListener listener;
public Photo(Context ourContext, OnPictureTakenListener theListener) {
this.context = ourContext;
this.listener = theListener;
}
#SuppressLint("SimpleDateFormat")
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Date date = new Date();
File pictureFileDir = getDir();
if (!pictureFileDir.exists() && !pictureFileDir.mkdirs())
{
Log.d(AndroidCamera.DEBUG_TAG,
"Can't create directory to save image.");
Toast.makeText(context, "Can't create directory to save image.",
Toast.LENGTH_LONG).show();
return;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
SimpleDateFormat dateConverter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
String exifDateString = dateFormat.format(date);
String datePicture = dateConverter.format(date);
String photoFile = "Picture_" + datePicture + ".jpg";
String photoFilePatched = "Picture_" + datePicture + "_patched.jpg";
String filename = pictureFileDir.getPath() + File.separator + photoFile;
String filenamePatched = pictureFileDir.getPath() + File.separator + photoFilePatched;
File pictureFile = new File(filename);
File pictureFilePatched = new File (filenamePatched);
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
FileOutputStream fosPatched = new FileOutputStream(pictureFilePatched);
fos.write(data);
fosPatched.write(data);
fos.close();
fosPatched.close();
Toast.makeText(context, "New Image saved:" + photoFile,
Toast.LENGTH_LONG).show();
} catch (Exception error) {
Log.d(AndroidCamera.DEBUG_TAG, "File" + filename + " not saved: "
+ error.getMessage());
Toast.makeText(context, "Image could not be saved.",
Toast.LENGTH_LONG).show();
}
listener.pictureTaken(pictureFile,pictureFilePatched,exifDateString);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
}
private File getDir() {
File sdDir = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
return new File(sdDir, "Camera");
}
static final int REQUEST_IMAGE_CAPTURE = 1;
}
here in main activity i call on one from the two pictures to write the exif infortmations:
camera.takePicture(null, null, new Photo(this,
new Photo.OnPictureTakenListener() {
public void pictureTaken(final File pictureFile,final File pictureFilePatched , final String date) {
final String fileName = pictureFile.getPath();
final String fileNamePatched = pictureFilePatched.getPath();
final String dateTime = date;
// don't start picture preview immediately, but a little
// delayed...
continueWithPreview.postDelayed(new Runnable() {
#Override
public void run() {
try {
// EXIF Matadata change
ExifHelper exifHelper = new ExifHelper();
exifHelper.createInFile(fileName);
//EXIF Metadata read
exifHelper.readExifData();
exifHelper.createOutFile(fileNamePatched);
//Exif Metadata write
exifHelper.writeExifData(dateTime);
} catch (IOException e) {
e.printStackTrace();
Log.e("PictureActivity", e.getLocalizedMessage());
}
if (null != camera)
{
camera.startPreview();
Toast.makeText(AndroidCamera.this, "started!",
Toast.LENGTH_SHORT).show();
}
}
}, 2500);
I have a device info app on Google Play, and within the app I have storage information. I know in Android 4.4 there has been some changes in regards to accessing external SDcards. Internal doesn't seem to give me a problem. My question is, how can I reliably get the size of SDcards on KitKat?
I have the required permissions listed, as this worked fine on earlier versions of Android. I have searched here on SO, and I always seem to get one of the same errors. I do not need to write to SDcards, only read for size availabilty.
I have a StorageUtils class that came from SO, sorry I can't remember the link.
public class StorageUtils {
private static final String TAG = "StorageUtils";
public static class StorageInfo {
public final String path;
public final boolean internal;
public final boolean readonly;
public final int display_number;
StorageInfo(String path, boolean internal, boolean readonly,
int display_number) {
this.path = path;
this.internal = internal;
this.readonly = readonly;
this.display_number = display_number;
}
}
public static ArrayList<StorageInfo> getStorageList() {
ArrayList<StorageInfo> list = new ArrayList<StorageInfo>();
String def_path = Environment.getExternalStorageDirectory().getPath();
boolean def_path_internal = !Environment.isExternalStorageRemovable();
String def_path_state = Environment.getExternalStorageState();
boolean def_path_available = def_path_state
.equals(Environment.MEDIA_MOUNTED)
|| def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
boolean def_path_readonly = Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
BufferedReader buf_reader = null;
try {
HashSet<String> paths = new HashSet<String>();
buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
int cur_display_number = 1;
Log.d(TAG, "/proc/mounts");
while ((line = buf_reader.readLine()) != null) {
Log.d(TAG, line);
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
String unused = tokens.nextToken(); // device
String mount_point = tokens.nextToken(); // mount point
if (paths.contains(mount_point)) {
continue;
}
unused = tokens.nextToken(); // file system
List<String> flags = Arrays.asList(tokens.nextToken()
.split(",")); // flags
boolean readonly = flags.contains("ro");
if (mount_point.equals(def_path)) {
paths.add(def_path);
list.add(new StorageInfo(def_path, def_path_internal,
readonly, -1));
} else if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure")
&& !line.contains("/mnt/asec")
&& !line.contains("/mnt/obb")
&& !line.contains("/dev/mapper")
&& !line.contains("tmpfs")) {
paths.add(mount_point);
list.add(new StorageInfo(mount_point, false,
readonly, cur_display_number++));
}
}
}
}
if (!paths.contains(def_path) && def_path_available) {
list.add(new StorageInfo(def_path, def_path_internal,
def_path_readonly, -1));
}
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (buf_reader != null) {
try {
buf_reader.close();
} catch (IOException ex) {
}
}
}
return list;
}
public static String getReadableFileSize(long bytes, boolean si) {
int unit = si ? 1000 : 1024;
if (bytes < unit)
return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1)
+ (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
#SuppressLint("NewApi")
public static long getFreeSpace(String path) {
StatFs statFs = new StatFs(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
long sdAvailSize = statFs.getFreeBlocksLong()
* statFs.getBlockSizeLong();
return sdAvailSize;
} else {
#SuppressWarnings("deprecation")
double sdAvailSize = (double) statFs.getFreeBlocks()
* (double) statFs.getBlockSize();
return (long) sdAvailSize;
}
}
public static long getUsedSpace(String path) {
return getTotalSpace(path) - getFreeSpace(path);
}
#SuppressLint("NewApi")
public static long getTotalSpace(String path) {
StatFs statFs = new StatFs(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
long sdTotalSize = statFs.getBlockCountLong()
* statFs.getBlockSizeLong();
return sdTotalSize;
} else {
#SuppressWarnings("deprecation")
double sdTotalSize = (double) statFs.getBlockCount()
* statFs.getBlockSize();
return (long) sdTotalSize;
}
}
/**
* getSize()[0] is /mnt/sdcard. getSize()[1] is size of sd (example 12.0G),
* getSize()[2] is used, [3] is free, [4] is blksize
*
* #return
* #throws IOException
*/
public static String[] getSize() throws IOException {
String memory = "";
Process p = Runtime.getRuntime().exec("df /mnt/sdcard");
InputStream is = p.getInputStream();
int by = -1;
while ((by = is.read()) != -1) {
memory += new String(new byte[] { (byte) by });
}
for (String df : memory.split("/n")) {
if (df.startsWith("/mnt/sdcard")) {
String[] par = df.split(" ");
List<String> pp = new ArrayList<String>();
for (String pa : par) {
if (!pa.isEmpty()) {
pp.add(pa);
}
}
return pp.toArray(new String[pp.size()]);
}
}
return null;
}
}
And here is my fragment in which I try to display the SDcard path and size.
public class CpuMemFragment extends Fragment {
// CPU
String devCpuInfo;
TextView tvCpuInfo;
// RAM
String devRamInfo;
TextView tvRamInfo;
// Storage
String devStorageA, devStorageB;
TextView tvStorageAName, tvStorageA, tvStorageB, tvStorageBName;
AdView adView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.cpu_mem, container, false);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
// *** CPU ***
//
devCpuInfo = readCpuInfo();
//
// #################################
// *** RAM ***
//
devRamInfo = readTotalRam();
//
// #################################
// *** STORAGE ***
//
ArrayList<StorageInfo> storageInfoList = StorageUtils.getStorageList();
tvStorageAName = (TextView) getView().findViewById(R.id.tvStorageAName);
tvStorageBName = (TextView) getView().findViewById(R.id.tvStorageBName);
if (storageInfoList.size() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& !storageInfoList.get(0).internal) {
kitKatWorkaround(0);
}
tvStorageAName.setText(storageInfoList.get(0).path);
devStorageA = StorageUtils.getReadableFileSize(
(StorageUtils.getUsedSpace(storageInfoList.get(0).path)),
true)
+ "/"
+ StorageUtils.getReadableFileSize((StorageUtils
.getTotalSpace(storageInfoList.get(0).path)), true);
if (storageInfoList.size() > 1) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& !storageInfoList.get(0).internal) {
kitKatWorkaround(1);
}
tvStorageBName.setText(storageInfoList.get(1).path);
devStorageB = StorageUtils.getReadableFileSize(
StorageUtils.getUsedSpace(storageInfoList.get(1).path)
+ (StorageUtils.getUsedSpace("system/")), true)
+ "/"
+ StorageUtils.getReadableFileSize((StorageUtils
.getTotalSpace(storageInfoList.get(1).path)),
true);
} else {
devStorageB = "N/A";
}
} else {
devStorageA = "N/A";
devStorageB = "N/A";
}
//
// #################################
// CPU
tvCpuInfo = (TextView) getView().findViewById(R.id.tvCpuInfo);
tvCpuInfo.setText(devCpuInfo);
//
// #################################
// RAM
tvRamInfo = (TextView) getView().findViewById(R.id.tvRamInfo);
tvRamInfo.setText(devRamInfo);
//
// #################################
// STORAGE
tvStorageA = (TextView) getView().findViewById(R.id.tvStorageA);
tvStorageA.setText(devStorageA);
tvStorageB = (TextView) getView().findViewById(R.id.tvStorageB);
tvStorageB.setText(devStorageB);
//
// #################################
// Look up the AdView as a resource and load a request.
adView = (AdView) getActivity().findViewById(R.id.adCpuMemBanner);
AdRequest adRequest = new AdRequest.Builder().build();
adView.loadAd(adRequest);
}
#Override
public void onPause() {
if (adView != null) {
adView.pause();
}
super.onPause();
}
#Override
public void onResume() {
super.onResume();
if (adView != null) {
adView.resume();
}
}
#Override
public void onDestroy() {
if (adView != null) {
adView.destroy();
}
super.onDestroy();
}
private static synchronized String readCpuInfo() {
ProcessBuilder cmd;
String result = "";
try {
String[] args = { "/system/bin/cat", "/proc/cpuinfo" };
cmd = new ProcessBuilder(args);
Process process = cmd.start();
InputStream in = process.getInputStream();
byte[] re = new byte[1024];
while (in.read(re) != -1) {
System.out.println(new String(re));
result = result + new String(re);
}
in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return result;
}
public static synchronized String readTotalRam() {
String load = "";
try {
RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
load = reader.readLine();
reader.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return load;
}
public void kitKatWorkaround(int index) {
String path1 = Environment.getExternalStorageDirectory().getPath();
//Storage A
if (index == 0) {
tvStorageAName.setText(path1);
devStorageA = StorageUtils.getReadableFileSize(
(StorageUtils.getUsedSpace(path1)), true)
+ "/"
+ StorageUtils.getReadableFileSize(
(StorageUtils.getTotalSpace(path1)), true);
}
//Storage B
if (index == 1) {
tvStorageBName.setText(path1);
devStorageB = StorageUtils.getReadableFileSize(
StorageUtils.getUsedSpace(path1)), true)
+ "/"
+ StorageUtils.getReadableFileSize(
(StorageUtils.getTotalSpace(path1)), true);
}
}
}
This results in the EACCES error, or an invalid path (access denied) on KitKat. Please help, and thank you greatly for your time.
You can find all shared storage devices by calling Context.getExternalFilesDirs() or Context.getExternalCacheDirs(). Then, you can call File.getUsableSpace() or use android.os.StatFs to check available disk space at each location.
These APIs are also available in ContextCompat in the support library to seamlessly work on pre-KitKat devices.
how can make the first look like BBC News android app using Asynctask (onPreExecute() method). please help me my helper class code is here...
UrlImageViewHelper.java
package com.warriorpoint.androidxmlsimple;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Hashtable;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.util.DisplayMetrics;
import android.widget.ImageView;
public final class UrlImageViewHelper {
private static final String LOGTAG = "UrlImageViewHelper";
public static int copyStream(InputStream input, OutputStream output) throws IOException
{
byte[] stuff = new byte[1024];
int read = 0;
int total = 0;
while ((read = input.read(stuff)) != -1)
{
output.write(stuff, 0, read);
total += read;
}
return total;
}
static Resources mResources;
static DisplayMetrics mMetrics;
private static void prepareResources(Context context) {
if (mMetrics != null)
return;
mMetrics = new DisplayMetrics();
Activity act = (Activity)context;
act.getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
AssetManager mgr = context.getAssets();
mResources = new Resources(mgr, mMetrics, context.getResources().getConfiguration());
}
private static BitmapDrawable loadDrawableFromStream(Context context, InputStream stream) {
prepareResources(context);
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
//Log.i(LOGTAG, String.format("Loaded bitmap (%dx%d).", bitmap.getWidth(), bitmap.getHeight()));
return new BitmapDrawable(mResources, bitmap);
}
public static final int CACHE_DURATION_INFINITE = Integer.MAX_VALUE;
public static final int CACHE_DURATION_ONE_DAY = 1000 * 60 * 60 * 24;
public static final int CACHE_DURATION_TWO_DAYS = CACHE_DURATION_ONE_DAY * 2;
public static final int CACHE_DURATION_THREE_DAYS = CACHE_DURATION_ONE_DAY * 3;
public static final int CACHE_DURATION_FOUR_DAYS = CACHE_DURATION_ONE_DAY * 4;
public static final int CACHE_DURATION_FIVE_DAYS = CACHE_DURATION_ONE_DAY * 5;
public static final int CACHE_DURATION_SIX_DAYS = CACHE_DURATION_ONE_DAY * 6;
public static final int CACHE_DURATION_ONE_WEEK = CACHE_DURATION_ONE_DAY * 7;
public static void setUrlDrawable(final ImageView imageView, final String url, int defaultResource) {
setUrlDrawable(imageView.getContext(), imageView, url, defaultResource, CACHE_DURATION_THREE_DAYS);
}
public static void setUrlDrawable(final ImageView imageView, final String url) {
setUrlDrawable(imageView.getContext(), imageView, url, null, CACHE_DURATION_THREE_DAYS);
}
public static void loadUrlDrawable(final Context context, final String url) {
setUrlDrawable(context, null, url, null, CACHE_DURATION_THREE_DAYS);
}
public static void setUrlDrawable(final ImageView imageView, final String url, Drawable defaultDrawable) {
setUrlDrawable(imageView.getContext(), imageView, url, defaultDrawable, CACHE_DURATION_ONE_DAY);
}
public static void setUrlDrawable(final ImageView imageView, final String url, int defaultResource, long cacheDurationMs) {
setUrlDrawable(imageView.getContext(), imageView, url, defaultResource, cacheDurationMs);
}
public static void loadUrlDrawable(final Context context, final String url, long cacheDurationMs) {
setUrlDrawable(context, null, url, null, cacheDurationMs);
}
public static void setUrlDrawable(final ImageView imageView, final String url, Drawable defaultDrawable, long cacheDurationMs) {
setUrlDrawable(imageView.getContext(), imageView, url, defaultDrawable, cacheDurationMs);
}
private static void setUrlDrawable(final Context context, final ImageView imageView, final String url, int defaultResource, long cacheDurationMs) {
Drawable d = null;
if (defaultResource != 0)
d = imageView.getResources().getDrawable(defaultResource);
setUrlDrawable(context, imageView, url, d, cacheDurationMs);
}
private static boolean isNullOrEmpty(CharSequence s) {
return (s == null || s.equals("") || s.equals("null") || s.equals("NULL"));
}
private static boolean mHasCleaned = false;
public static String getFilenameForUrl(String url) {
return "" + url.hashCode() + ".urlimage";
}
private static void cleanup(Context context) {
if (mHasCleaned)
return;
mHasCleaned = true;
try {
// purge any *.urlimage files over a week old
String[] files = context.getFilesDir().list();
if (files == null)
return;
for (String file : files) {
if (!file.endsWith(".urlimage"))
continue;
File f = new File(context.getFilesDir().getAbsolutePath() + '/' + file);
if (System.currentTimeMillis() > f.lastModified() + CACHE_DURATION_ONE_WEEK)
f.delete();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
private static void setUrlDrawable(final Context context, final ImageView imageView, final String url, final Drawable defaultDrawable, long cacheDurationMs) {
cleanup(context);
// disassociate this ImageView from any pending downloads
if (imageView != null)
mPendingViews.remove(imageView);
if (isNullOrEmpty(url)) {
if (imageView != null)
imageView.setImageDrawable(defaultDrawable);
return;
}
final UrlImageCache cache = UrlImageCache.getInstance();
Drawable d = cache.get(url);
if (d != null) {
//Log.i(LOGTAG, "Cache hit on: " + url);
if (imageView != null)
imageView.setImageDrawable(d);
return;
}
final String filename = getFilenameForUrl(url);
File file = context.getFileStreamPath(filename);
if (file.exists()) {
try {
if (cacheDurationMs == CACHE_DURATION_INFINITE || System.currentTimeMillis() < file.lastModified() + cacheDurationMs) {
//Log.i(LOGTAG, "File Cache hit on: " + url + ". " + (System.currentTimeMillis() - file.lastModified()) + "ms old.");
FileInputStream fis = context.openFileInput(filename);
BitmapDrawable drawable = loadDrawableFromStream(context, fis);
fis.close();
if (imageView != null)
imageView.setImageDrawable(drawable);
cache.put(url, drawable);
return;
}
else {
//Log.i(LOGTAG, "File cache has expired. Refreshing.");
}
}
catch (Exception ex) {
}
}
// null it while it is downloading
if (imageView != null)
imageView.setImageDrawable(defaultDrawable);
// since listviews reuse their views, we need to
// take note of which url this view is waiting for.
// This may change rapidly as the list scrolls or is filtered, etc.
//Log.i(LOGTAG, "Waiting for " + url);
if (imageView != null)
mPendingViews.put(imageView, url);
ArrayList<ImageView> currentDownload = mPendingDownloads.get(url);
if (currentDownload != null) {
// Also, multiple vies may be waiting for this url.
// So, let's maintain a list of these views.
// When the url is downloaded, it sets the imagedrawable for
// every view in the list. It needs to also validate that
// the imageview is still waiting for this url.
if (imageView != null)
currentDownload.add(imageView);
return;
}
final ArrayList<ImageView> downloads = new ArrayList<ImageView>();
if (imageView != null)
downloads.add(imageView);
mPendingDownloads.put(url, downloads);
AsyncTask<Void, Void, Drawable> downloader = new AsyncTask<Void, Void, Drawable>() {
#Override
protected Drawable doInBackground(Void... params) {
AndroidHttpClient client = AndroidHttpClient.newInstance(context.getPackageName());
try {
HttpGet get = new HttpGet(url);
final HttpParams httpParams = new BasicHttpParams();
HttpClientParams.setRedirecting(httpParams, true);
get.setParams(httpParams);
HttpResponse resp = client.execute(get);
int status = resp.getStatusLine().getStatusCode();
if(status != HttpURLConnection.HTTP_OK){
// Log.i(LOGTAG, "Couldn't download image from Server: " + url + " Reason: " + resp.getStatusLine().getReasonPhrase() + " / " + status);
return null;
}
HttpEntity entity = resp.getEntity();
// Log.i(LOGTAG, url + " Image Content Length: " + entity.getContentLength());
InputStream is = entity.getContent();
FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
copyStream(is, fos);
fos.close();
is.close();
FileInputStream fis = context.openFileInput(filename);
return loadDrawableFromStream(context, fis);
}
catch (Exception ex) {
// Log.e(LOGTAG, "Exception during Image download of " + url, ex);
return null;
}
finally {
client.close();
}
}
#Override
protected void onPostExecute(Drawable result) {
if (result == null)
result = defaultDrawable;
mPendingDownloads.remove(url);
cache.put(url, result);
for (ImageView iv: downloads) {
// validate the url it is waiting for
String pendingUrl = mPendingViews.get(iv);
if (!url.equals(pendingUrl)) {
//Log.i(LOGTAG, "Ignoring out of date request to update view for " + url);
continue;
}
mPendingViews.remove(iv);
if (result != null) {
final Drawable newImage = result;
Drawable newSize=resize(newImage);
final ImageView imageView = iv;
imageView.setImageDrawable(newSize);
}
}
}
private BitmapDrawable resize(Drawable newImage) {
// TODO Auto-generated method stub
Bitmap d = ((BitmapDrawable)newImage).getBitmap();
Bitmap bitmapOrig = Bitmap.createScaledBitmap(d, 75, 75, false);
return new BitmapDrawable(bitmapOrig);
}
};
downloader.execute();
}
private static Hashtable<ImageView, String> mPendingViews = new Hashtable<ImageView, String>();
private static Hashtable<String, ArrayList<ImageView>> mPendingDownloads = new Hashtable<String, ArrayList<ImageView>>();
}
my main activity is MessageList.java.
Like this
private final ProgressDialog dialog = new ProgressDialog(this.context);
#Override
protected void onPreExecute() {
this.dialog.setMessage("Fecthing Image");
this.dialog.setTitle("Please Wait");
this.dialog.setIcon(R.drawable."Any Image here");
this.dialog.show();
}