I really need some help please!
I want to make a MP3 Player on Android and therefore I need to find all MP3 files in the storage.
So I search them recursively in my get MP3 method:
private void getMP3Files(Context context, String directory,ArrayList<MusicListArray> mp3_list){
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
Uri uri;
byte[] album_art;
Bitmap bitmap;
/*File[] files = Directory.listFiles(new MP3FileNameFilter());
files = Directory.listFiles();*/
File folder = new File(directory);
for (File file : folder.listFiles()) {
if (file.isFile()) {
if (file.getName().endsWith(".mp3") || file.getName().endsWith(".MP3")) {
uri = Uri.fromFile(file);
mediaMetadataRetriever.setDataSource(context,uri);
String artist = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
String title = file.getName();
album_art = mediaMetadataRetriever.getEmbeddedPicture();
if(album_art != null){
bitmap = BitmapFactory.decodeByteArray(album_art, 0, album_art.length);
}else{
bitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_launcher);
}
if(bitmap == null){
bitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_launcher);
}
if (title.indexOf(".") > 0)
title = title.substring(0, title.lastIndexOf("."));
if(artist == null){
artist = getString(R.string.unknown_artist);
}
mp3_list.add(new MusicListArray(title,artist,file,bitmap));
}
}else if(file.isDirectory()){
getMP3Files(context, file.getPath(), mp3_list);
}
}
Collections.sort(mp3_list);
/*for(int i=0;i<files.length;i++){
mp3_list.add(new MusicListArray(files[i].getName(),"Test",files[i]));
}*/
//return mp3_list;
}
This is very slow and needs about 3 seconds everytime I start my app.
First question: How do I manage to reduce the taken time?
Second question: How to save the list in a file and load it?
Thanks in advance!
I suggest you use an SQL database to store the metadata (including file location) of your mp3s. I should warn you that I have no experience with media on Android though. Basic Idea:
1) First Time use (or on a manual sync in settings) do a full 3 second sync -> look at SharedPreferences to store a key indicating first time use for your app
2) Store this meta information in a SQL database.
3) Make the app read from the database as this is much faster, especially if the table is designed correctly. Store things like cover art, file location, artist, title etc. You can also add things like number of times listened and stuff. In addition if you wrap your SQL layer into a content provider you can perform custom searches of all your music.
4) When app is running, run a background indexing service that finds new music, corrects changes/deletes etc. Look into android Service
The benefits of this is that your app will be much faster to the user, only need to load all the data once in the foreground and provide you with a lot more flexibility in terms of searching, song choice, custom data etc.
Related
Im interested in running some custom analytics on my interactions with contacts on my phone.
Some things I would like to see are:
How many times was this contact called
How long did the conversation last
How many missed calls from this contact
How many answered calls from this contact
What time and date was an sms sent
What was its message content
What time and date was an sms received
What was its message content
If it was mms can i get the picture some how
Ill use a third party api for facial recognition and nudity checks (was it a nude, selfie, meme)
Is there a way to simply export this data into a xml or csv file? (How would I save pictures?)
My goal here is to make an app using some sort of android java sdk. Then using the app, ill upload to my web server and use php to do the analytics.
Where do i look to start getting the information i want to analyze?
Try to look at these links:
PhoneStateListener
TelephonyManager
Read SMS
ContentResolver
To export your pictures from mms use a filestream and a bitmap:
private void GetMmsAttachment(String _id, String _data)
{
Uri partURI = Uri.parse("content://mms/part/" + _id );
String filePath = "/sdcard/photo.jpg";
InputStream is = null;
OutputStream picFile = null;
Bitmap bitmap = null;
try {
is = getContentResolver().openInputStream(partURI);
bitmap = BitmapFactory.decodeStream(is);
picFile = new FileOutputStream(filePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, picFile);
picFile.flush();
picFile.close();
}
catch (Exception e)
{
e.printStackTrace();
//throw new MmsException(e);
}
}
Also for photo Identification I recommend you use IBM Watson,.If the photo has text use Google Tesseract to extract the text.
I created an Android mobile with Xamarin Android. There is a feature inside that app that can send an image to twitter. I tested this feature after downloading the app from the play store on the Samsung S7, the alcatel Pixi4 and my tablet (Galaxy Tab 2). My code creates a bitmap file in the external cachedirectory and requests twitter to read this file in order to have it attached to a tweet.
It works fine. However, I asked other people to test it (they also downloaded my app from the play store) and they told me the file is created in the cache directory but the bitmap picture is not attached to their tweets. It is also important to know that other apps that do that (such as long tweet) work on all devices:
https://play.google.com/store/apps/details?id=de.cbruegg.longtweet
I don't understand. Twitter, works on all devices. This has been checked. Apps (such as long tweet) can send a file to twitter to tweet this. This also works on all devices. Moreover, my app perfectly writes files to the cache directory. Works on all devices. In addition, my app is able to send a bitmap picture to twitter in order to tweet the picture. This works on my S7. But not on the S7 of my tester. I don't get it. Here is my code. Someway, I am doing something wrong. By the way, generating the tweets always works. It is just the case that the picture is not attached to the tweet when testing on the device of my tester.
Here is my code. Please let me know how I should improved it.
public bool TweetImage(Bitmap imageToTweet)
{
var messageIntent = context.FindMessageIntent(this.twitterConstants.PackageName);
if (messageIntent == null)
{
return false;
}
string outputFileBMP = SaveBitmap(imageToTweet);
context.Tweet(messageIntent, outputFileBMP, this.twitterConstants.DefaultTwitterText, this.twitterConstants.ChooserMessage);
return true;
}
private string SaveBitmap(Bitmap imageToTweet)
{
string outputFileBMP = System.IO.Path.Combine(context.ExternalCacheDir.Path, System.Guid.NewGuid().ToString() + ".bmp");
using (var outputFileStream = System.IO.File.Create(outputFileBMP))
{
imageToTweet.Compress(Android.Graphics.Bitmap.CompressFormat.Png, 100, outputFileStream);
}
return outputFileBMP;
}
and
public static Intent FindMessageIntent(this ContextWrapper contextWrapper, params string[] packageNames)
{
Intent wantedIntent = new Intent();
wantedIntent.SetType("text/plain");
var resolveInfos = contextWrapper.PackageManager.QueryIntentActivities(wantedIntent, PackageInfoFlags.MatchDefaultOnly);
var result = (from r in resolveInfos
from p in packageNames
where p == r.ActivityInfo.PackageName
select p).FirstOrDefault();
if (result != null)
{
wantedIntent.SetPackage(result);
return wantedIntent;
}
return null;
}
and
public static void Tweet(this ContextWrapper contextWrapper, Intent messageIntent, string filePath = null, string message = null, string chooserMessage = null)
{
if (filePath != null)
{
using (var file = new Java.IO.File(filePath))
{
messageIntent.PutExtra(Intent.ExtraStream, Android.Net.Uri.FromFile(file));
}
}
if (message != null)
{
messageIntent.PutExtra(Intent.ExtraText, message);
}
if (chooserMessage != null)
{
using (var chooser = Intent.CreateChooser(messageIntent, chooserMessage))
{
contextWrapper.StartActivity(chooser);
}
return;
}
contextWrapper.StartActivity(messageIntent);
}
Since you're using the external cache directory, you should always make sure it exists before using it. From the Android documentation:
The external storage may be unavailable—such as when the user has mounted the storage to a PC or has removed the SD card that provides the external storage—you should always verify that the volume is available before accessing it. You can query the external storage state by calling getExternalStorageState(). If the returned state is equal to MEDIA_MOUNTED, then you can read and write your files.
S7 supports MicroSD cards so maybe this is the case with your tester?
Also, Long Tweet does the image storing and sharing slightly differently than you. Here are the differences:
They store the generated bitmap to the internal cache directory (CacheDir instead of ExternalCacheDir).
They set the read and write permissions (Readable and Executable properties of the File object) to the resulting file and directory, so that other apps can access the file when they have the full path.
For the record, here's the Java code for how they set up the intent:
public static void intentImage(Context context, File file, String intentAction)
{
file.getParentFile().getParentFile().setExecutable(true, false);
file.getParentFile().setExecutable(true, false);
file.getParentFile().getParentFile().setReadable(true, false);
file.getParentFile().setReadable(true, false);
file.setReadable(true, false);
file.setExecutable(true, false);
String fileStr = "file://" + file.toString();
Intent intent = new Intent();
intent.setAction(intentAction);
if (intentAction == "android.intent.action.SEND")
{
intent.putExtra("android.intent.extra.STREAM", Uri.parse(fileStr));
intent.setType("image/" + fileStr.substring(fileStr.lastIndexOf(".") + 1));
}
else
{
intent.setDataAndType(Uri.parse(fileStr), "image/" + fileStr.substring(fileStr.lastIndexOf(".") + 1));
}
context.startActivity(intent);
}
I would start by utilizing the internal cache directory and setting the correct permissions to the file generated (possibly also the parent folder).
I'm using SAF so that users can store videos on their external SD cards or even USB drives with my camera app. I don't want them to appear in the gallery though, so I need to create a .nomedia file.
I'm using this code currently:
mTreeUri = Uri.parse(treeUri);
mPickedDir = DocumentFile.fromTreeUri(this, mTreeUri);
if (treeUri != null) {
final DocumentFile existingMediaFile = mPickedDir.findFile(".nomedia");
if (existingMediaFile == null) {
DocumentFile newFile = mPickedDir.createFile("text/plain", ".nomedia");
if (newFile == null) {
return null;
}
OutputStream out = null;
try {
out = getContentResolver().openOutputStream(newFile.getUri());
out.write("A long time ago...".getBytes());
out.close();
} catch (IOException e) {
Log.d(TAG, "Nomedia", e);
}
The problem is that SAF creates a file called nomedia.txt instead of the dotfile. I played around with a few different mime types in the application/* range but then no file gets created at all. Glad for any pointers :)
And found it. Using any kind of custom mime type like "thisis/awesome" seems to work just fine.
A little bit late for an answer, but I would suggest something like:
new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_MUSIC), ".nomedia").createNewFile();
"application/octet-stream" works fine. That's the default mime type for cases when something more specific doesn't apply.
I'm writing an android app that contains about 500 images .
there are somethings that make me worry, I don't want to use internet.
1-the application size will be very big , is there anyway to moving images to sd card while installing? some devices may don't have this amount of space on the phone .
2-should I make 3 images for hdpi , ldpi and mdpi ?
You can put you image in asset folder. If you want to transfer image from assets to SD Card then you can't do like this.
But you can do by one way. You put your image on server and at 1st time when you will open app you can download it and save it in SD Card and then access from there.
Yes, it will be big. No, you can't remove them from your package.
No, you can make only hdpi images. Android will scale them automatically (which may slow down a bit the app).
Suggestion - use internet. Since the user has internet to download your app, he can wait to download the resources on first start. Also it give you the ability to add/remove files via online configuration. Just imagine if you have to add 1 image and upload new version - this means that the user will have to download the same huge package again.
I had a similar requirement - include a bunch of images in the app, but in my case, the image had to be accessible by any user or app, not just the app that unpacked them. I stored them in the res/raw folder and copied them to user space on start up:
private void loadCopyResources() {
// copy resources to space any activity can use
String sourceName;
String resourceName;
String fileName;
int resource;
String typeName = sourceSink.Types.photo.toString();
for (sourceSink.Sources source: sourceSink.Sources.values() ){
for (int i = 0; i< photoFileCount; i++) {
sourceName = source.toString();
resourceName = sourceName + "_" + typeName + (i+1); // i.e. dropbox_photo2
fileName = resourceName + ".jpg"; // files requires extension
resource = getResources().getIdentifier(resourceName, "raw", "com.example.myapp");
createExternalStoragePublicFile(typeName,fileName, resource); // copy it over
}
}
}
void createExternalStoragePublicFile(String fType, String fname, int res ) {
// Create a path where we will place our picture in the user's
// public pictures directory. Note that you should be careful about
// what you place here, since the user often manages these files. For
// pictures and other media owned by the application, consider
// Context.getExternalMediaDir().
File path = null;
if (((fType.equals(sourceSink.Types.photo.toString())) || (fType.equals(sourceSink.Types.file.toString())) ) ){
path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
}
if (fType.equals(sourceSink.Types.music.toString())) {
path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MUSIC);
}
if (fType.equals(sourceSink.Types.video.toString())) {
path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES);
}
File file = new File(path, "/" + fname);
try {
// Make sure the Pictures directory exists.
path.mkdirs();
// Very simple code to copy a picture from the application's
// resource into the external file. Note that this code does
// no error checking, and assumes the picture is small (does not
// try to copy it in chunks). Note that if external storage is
// not currently mounted this will silently fail.
InputStream is = getResources().openRawResource(res);
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
scanMedia(file);
} catch (IOException e) {
// Unable to create file, likely because external storage is
// not currently mounted.
Log.w("ExternalStorage", "Error writing " + file, e);
}
}
sourceSink, which I didn't include, is just a list of file names and file types I needed copied.
I have an application in which I can use the device's camera to take a picture. What I would like to do is to start the ACTION_IMAGE_CAPTURE intent without assigning an EXTRA_OUTPUT, and then move the file that is created in the default location to my own custom location using file.renameTo. My code is something like this:
/* Start camera activity without EXTRA_OUTPUT */
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, _REQUESTCODE_ATTACH_CAMERA);
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch(requestCode) {
case _REQUESTCODE_ATTACH_CAMERA:
/* Get path to most recently added image */
final String[] imageColumns = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA };
final String imageOrderBy = MediaStore.Images.Media._ID + " DESC";
Cursor imageCursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageColumns, null, null, imageOrderBy);
String fullPath = "";
if(imageCursor.moveToFirst()){
fullPath = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA));
imageCursor.close();
}
File f = Environment.getExternalStorageDirectory();
f = new File(f.getAbsolutePath() + File.separator + "DCIM" + File.separator + MY_APP_NAME;
if(!f.exists()) {
f.mkdirs();
}
/* Create new file based on name of most recently created image */
File oldFile = new File(fullPath);
String newPath = f.getAbsolutePath() + File.separator + oldFile.getName() ;
/* Move file with renameTo */
oldFile.renameTo(new File(newPath));
break;
...
}
}
}
All of this works quite well, however there is one strange thing that is occurring. In my app, I have another button that allows selecting an existing image from the phone's gallery. That code looks like this:
Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
activity.startActivityForResult(galleryIntent, _REQUESTCODE_ATTACH_GALLERY);
This also works, but if I take a picture with the camera using the code posted above, and then try to select another image from the gallery, there will be blank "broken link" type items in the gallery that contain no content and are unselectable. These seem to correspond with photos taken and moved using renameTo; if I put in code in onActivityResult to post the filename to LogCat, the name that gets logged is the same as the name of the previously moved file that it corresponds to. Trying to create a File object or in any way access that filename, results in null objects and force closes.
The strange part is that there is no evidence of these "broken link" files in Eclipse DDMS, nor in the phone itself if I use Root Browser, and they disappear if I remount the SD Card.
The whole reason I am moving the images after capturing them with the camera is to avoid filling up the phone's gallery storage with unnecessary images. While these empty "broken link" type files don't appear to be taking up any storage space, they would still be very annoying to an end-user trying to browse through their gallery. Does anyone have any ideas on what is happening here or how to solve this problem?
EDIT:
Here is a photo showing what the gallery looks like with a "broken link" type image displayed. One of these will appear for every photo that is taken using my app, and they will all disappear if I remount the SD Card.
Thanks in part to this SO thread, I have discovered a solution. It actually makes sense that it would behave this way since there is a table kept for media content and so removing something without telling the table would definitely create a "broken link" type scenario.
The ultimate solution is to use contentResolver.delete to remove the reference to the file within the content resolver, but there are two different ways that I have found that will work.
/* Moving with renameTo */
//Use the same exact code as I had before (shortened for brevity) to move the file
oldFile.renameTo(newFile);
//Get URI from contentResolver using file Id from cursor
Uri oldUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media._ID)));
//Delete old file
getContentResolver().delete(oldUri, null, null);
Getting the URI in this way is necessary because it requires a reference to the image in the contentResolver rather than the path to its location in storage. This way might feel dirty to some since you are moving a file and then calling a delete function on that file in order to sort of trick the content resolver into removing the link to the file. If you would rather, you can do it without using renameTo so that the call to delete(...) actually does delete the image.
/* Moving with streams */
//Get streams
InputStream in = new FileInputStream(oldFile);
OutputStream out = new FileOutputStream(newFile);
byte[] buffer = new byte[1024];
int bytesRead = 0;
//Read old file into new file
while((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
//Get URI from contentResolver using file Id from cursor
Uri oldUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media._ID)));
//Delete old file
getContentResolver().delete(oldUri, null, null);
The call to contentResolver.delete is the same either way, I just wanted to point out that it will still work if the image has already been removed.
During this I discovered a solution to a problem that I didn't even realize that I had that I will post here as well in case anyone with this same problem comes across this in the future. In order to keep the image as selectable in the device gallery from the new location, you need to let the media scanner know that a change has been made. There are two ways that I found to do this:
/* This is the only way that I know of to handle multiple new files at once. I
really would use this sparingly, however, since it will rescan the entire
SD Card. Not only could this take a long time if the user has a lot of files
on their card, it will also show a notification so it is not exactly a
transparent operation. */
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
/* You *could* do multiple files with this by passing in the path for each one
in the array of Strings, however an instance of this will get called for each
one rather than it doing them all at once. Likewise, your onScanCompleted
(if you choose to include one) will get called once for each file in the list.
So really, while this is much better for a small number of files, if you plan
on scanning a very large amount then the full rescan above would probably be
a better option. */
MediaScannerConnection.scanFile(context, new String[]{ newFilePathAsString }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
//This executes when scanning is completed
}
}
);