Public Key Cryptography on Android - android

I know that there is code like this:
http://www.java2s.com/Code/Android/Security/RSAencryptdecryptfunctionRSAECBPKCS1Padding.htm
/*
Copyright (c) 2010, Sungjin Han <meinside#gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of meinside nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
//package org.andlib.helpers;
public static KeyPair generateRsaKeyPair(int keySize, BigInteger publicExponent)
{
KeyPair keys = null;
try
{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(keySize, publicExponent);
keyGen.initialize(spec);
keys = keyGen.generateKeyPair();
}
catch(Exception e)
{
// Logger.e(e.toString());
}
return keys;
}
/**
* generates a RSA public key with given modulus and public exponent
*
* #param modulus (must be positive? don't know exactly)
* #param publicExponent
* #return
*/
public static PublicKey generateRsaPublicKey(BigInteger modulus, BigInteger publicExponent)
{
try
{
return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
}
catch(Exception e)
{
// Logger.e(e.toString());
}
return null;
}
/**
* generates a RSA private key with given modulus and private exponent
*
* #param modulus (must be positive? don't know exactly)
* #param privateExponent
* #return
*/
public static PrivateKey generateRsaPrivateKey(BigInteger modulus, BigInteger privateExponent)
{
try
{
return KeyFactory.getInstance("RSA").generatePrivate(new RSAPrivateKeySpec(modulus, privateExponent));
}
catch(Exception e)
{
// Logger.e(e.toString());
}
return null;
}
/**
* RSA encrypt function (RSA / ECB / PKCS1-Padding)
*
* #param original
* #param key
* #return
*/
public static byte[] rsaEncrypt(byte[] original, PublicKey key)
{
try
{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(original);
}
catch(Exception e)
{
// Logger.e(e.toString());
}
return null;
}
/**
* RSA decrypt function (RSA / ECB / PKCS1-Padding)
*
* #param encrypted
* #param key
* #return
*/
public static byte[] rsaDecrypt(byte[] encrypted, PrivateKey key)
{
try
{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(encrypted);
}
catch(Exception e)
{
// Logger.e(e.toString());
}
return null;
}
Some questions:
Are there side attacks for the code linked above, or is it secure?
If the answer to the first question is no, then is there a module which takes care of side attacks?
I am using Froyo and have tried googling for a while now. All help is appreciated, thanks.

What do you mean by 'side attacks'? Whether it's secure or not, depends on many things, mostly on how you use it, and what are you encrypting/decrypting. Funny thing is, the license is longer than the code, and code basically does nothing special: just swallowing exceptions...
This has little to do with Android, BTW.

Related

How to send 7 bit commands to M1 card by NFC on Android?

I'm having trouble using NFC on Android. I want to send some data to M1 card. I can send commands using the transceive() method. But the transceive() method has a byte[] argument, and a byte is 8 bit. I want to send 0b1000000 (short frame) to my M1 card. How send that to M1 card?
/**
* Send raw NfcA data to a tag and receive the response.
*
* <p>This is equivalent to connecting to this tag via {#link NfcA}
* and calling {#link NfcA#transceive}. Note that all MIFARE Classic
* tags are based on {#link NfcA} technology.
*
* <p>Use {#link #getMaxTransceiveLength} to retrieve the maximum number of bytes
* that can be sent with {#link #transceive}.
*
* <p>This is an I/O operation and will block until complete. It must
* not be called from the main application thread. A blocked call will be canceled with
* {#link IOException} if {#link #close} is called from another thread.
*
* <p class="note">Requires the {#link android.Manifest.permission#NFC} permission.
*
* #see NfcA#transceive
*/
public byte[] transceive(byte[] data) throws IOException {
return transceive(data, true);
}
My code is:
/**
* Write a block of 16 byte data to tag.
* #param sectorIndex The sector to where the data should be written
* #param blockIndex The block to where the data should be written
* #param data 16 byte of data.
* #param key The MIFARE Classic key for the given sector.
* #param useAsKeyB If true, key will be treated as key B
* for authentication.
* #return The return codes are:<br />
* <ul>
* <li>0 - Everything went fine.</li>
* <li>1 - Sector index is out of range.</li>
* <li>2 - Block index is out of range.</li>
* <li>3 - Data are not 16 bytes.</li>
* <li>4 - Authentication went wrong.</li>
* <li>-1 - Error while writing to tag.</li>
* </ul>
* #see #authenticate(int, byte[], boolean)
*/
public int writeBlock(int sectorIndex, int blockIndex, byte[] data,
byte[] key, boolean useAsKeyB) {
if (getSectorCount()-1 < sectorIndex) {
return 1;
}
if (mMFC.getBlockCountInSector(sectorIndex)-1 < blockIndex) {
return 2;
}
if (data.length != 16) {
return 3;
}
int block = mMFC.sectorToBlock(sectorIndex) + blockIndex;
// write chinese card
if (block == 0) {
// Write block.
try {
mMFC.transceive(new byte[]{(byte)0x50, (byte)0x00, (byte)0x57, (byte)0xCD});
mMFC.transceive(new byte[]{(byte)0x40}); // TODO: here need send 0b1000000(7 bit) , not 0b01000000(8 bit)
mMFC.transceive(new byte[]{(byte)0x43});
mMFC.writeBlock(block, data);
} catch (IOException e) {
Log.e(LOG_TAG, "Error while writing block to tag.", e);
return -1;
}
return 0;
}
if (!authenticate(sectorIndex, key, useAsKeyB)) {
return 4;
}
// Write block.
try {
mMFC.writeBlock(block, data);
} catch (IOException e) {
Log.e(LOG_TAG, "Error while writing block to tag.", e);
return -1;
}
return 0;
}
What you are trying to do is simply not possible on Android. The Android NFC API does not provide any method to send short frames (7 bit frame as defined in ISO/IEC 14443-3 Type A) directly.
In fact, the transceive() method of NfcA does not only send the byte array that is passed as its argument. Instead, it will also cause the CRC checksum to be automatically appended to the frame. As a consequence, you can only exchange normal frames (as defined in ISO/IEC 14443-3 Type A) using the transceive() method.
Since you are using MIFARE Classic (or rather some UID-changable clones), you will experience even further limitations on Android devices that support MIFARE Classic (i.e. devices with an NFC chipset manufactured by NXP): If a tag is detected as MIFARE Classic, the NFC controller will be switched into a special mode where it interprets high-level versions of the MIFARE Classic commands and automatically translates them to their low-level (and potentially encrypted) versions. This is necessary since MIFARE Classic does not fully follow the framing rules of ISO/IEC 14443-3 Type A. Unfortunately, this also typically prevents you from sending any raw frames directly to these tags at all.

java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block

I'm using RSA encrypt text and decrypt text. The public key and the private key are generated with openssl tool.
I encountered an "java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception when decrypting data.
Here is the RSA util class:
package studio.uphie.app;
import android.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* Created by Uphie on 2016/4/11.
*/
public class RSA {
private static String RSA = "RSA";
/**
*
* #param text text to be encrypted
* #param pub_key rsa public key
* #return encrypted data in byte-array form
*/
public static byte[] encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes();
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes(), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* #param text text to be decrypted
* #param pri_key rsa private key
* #return
*/
public static byte[] decryptData(String text, String pri_key) {
try {
byte[] data = text.getBytes();
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes(),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
} catch (Exception e) {
//"java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception occurs here.
return null;
}
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePublic(keySpec);
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException,
InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePrivate(keySpec);
}
}
And the snippet that encrypts and decrypts data:
//encrypt
byte[] e = RSA.encryptData(text, PUBLIC_KEY);
String result = Base64.encodeToString(e, Base64.DEFAULT);
tv_encrypted.setText(result);
//decrypt
byte[] d = RSA.decryptData(text, PRIVATE_KEY);
String result = Base64.encodeToString(d, Base64.DEFAULT);
tv_decrypted.setText("Decrypted result:\n" + result);
I know the reason may be that the text to be decrypted is too long , but I just encrypt "abc" and then decrypt the encrypted "abc". And how to handle encrypting long text if the text to be encrypted or decrypted should be 11 bytes less than the rsa private key? How can I do to solve it? I'm new to RSA.
Thanks in advance!
You are missing some steps in your code which makes it impossible to check. However, there are a few clues to suggest a problem. Your decryptData method takes a String argument and then calls String.getBytes() to get the data which is then decrypted. However, the result of encryption is a sequence of bytes which is not the encoding of any valid String. Perhaps you meant to base64 decode the input instead of calling getBytes(). In general to perform decryption and decoding you must reverse the steps you performed during encryption and encoding. So, if the plaintext is a byte[] then the steps are:
byte [] → Encrypt → byte [] → Base64 encode → String.
then, in the decrypt direction you start with a Base64 string, you must, in order:
String → Base64 decode → byte [] → decrypt → byte []
Also, another issue which is bad practice and a source of many portability bugs is the use of defaults. You are using defaults in two places and they're both troublesome. First you are using the default no-args String.getBytes() method, and presumably matching that up with the one-arg String (byte []) constructor. This use the platform default character set, but this can differ on different platforms. Therefore always specify a character set. For most applications 'UTF-8' is an ideal choice. Secondly, you are calling Cipher.getInstance('RSA') without specifying padding. Oracle's Java and Android's Java will give you different padding and thus your code will not be portable between the platforms. Always specify the complete padding string. Here the choice is little more difficult if you need portability to older Java implementations. OAEP padding should be your first choice, so Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); is probably the right choice. See this for further discussion.
As for how to encrypt longer texts, see the answer from Henry.
Fianlly I modified my codes like that and they work well:
public static String encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes("utf-8");
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes("utf-8"), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeToString(cipher.doFinal(data),Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String decryptData(String text, String pri_key) {
try {
byte[] data =Base64.decode(text,Base64.DEFAULT);
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes("utf-8"),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data),"utf-8");
} catch (Exception e) {
return null;
}
}
If something seems wrong still you can remind me. Thanks for James and Henry's answer.
Usually, you generate a random secret key for a symmetric cipher (like AES) and use this to encrypt your pay load.
RSA is then only used to encrypt this random key. This does not only solve the length problem but has some other advantages as well:
Symmetric cyphers are usually much faster
If the message is sent to several recievers, only the encrypted key has to be added specifically for each receiver, the main content can be the same.

Encrypting file with Conceal

I've been looking at using Conceal to encrypt some files. The code snippet provided states that the input is plain text. Can it be used to encrypt file binary though? Why is it specifically plain text and not general binary?
Here's the snippet provided:
// Creates a new Crypto object with default implementations of
// a key chain as well as native library.
Crypto crypto = new Crypto(
new SharedPrefsBackedKeyChain(context),
new SystemNativeCryptoLibrary());
// Check for whether the crypto functionality is available
// This might fail if Android does not load libaries correctly.
if (!crypto.isAvailable()) {
return;
}
OutputStream fileStream = new BufferedOutputStream(
new FileOutputStream(file));
// Creates an output stream which encrypts the data as
// it is written to it and writes it out to the file.
OutputStream outputStream = crypto.getCipherOutputStream(
fileStream,
entity);
// Write plaintext to it.
outputStream.write(plainText);
outputStream.close();
From the library, the method says:
/**
* A convenience method to encrypt data if the data to be processed is small and can
* be held in memory.
* #param plainTextBytes Bytes of the plain text.
* #param entity Entity to process.
* #return cipherText.
* #throws KeyChainException
* #throws CryptoInitializationException
* #throws IOException
* #throws CryptoInitializationException Thrown if the crypto libraries could not be initialized.
* #throws KeyChainException Thrown if there is trouble managing keys.
*/
public byte[] encrypt(byte[] plainTextBytes, Entity entity)
throws KeyChainException, CryptoInitializationException, IOException {
int cipheredBytesLength = plainTextBytes.length + mCipherHelper.getCipherMetaDataLength();
FixedSizeByteArrayOutputStream outputStream = new FixedSizeByteArrayOutputStream(cipheredBytesLength);
OutputStream cipherStream = mCipherHelper.getCipherOutputStream(outputStream, entity);
cipherStream.write(plainTextBytes);
cipherStream.close();
return outputStream.getBytes();
}
You will need to convert your plain text into byte[] in order to use the library. If you can also convert the binary file to byte[] then you can use conceal for your purposes.
Try this method:
byte[] bytes = File.ReadAllBytes("C:\\Mybinaryfile");

Secure random number generation for all Android versions

I am currently playing around with the idea of an application for android which involves encryption. I am planning to use aes in ctr mode and PBKDF2 with whirlpool for key stretching.
I am going to implement a new bouncy castle implementation instead of androids built in old implementation. To make sure it works as intended on any android version.
But I am having some problem figuring out a solid way to generate random numbers for salt and key. I have read somewhere that the built in secure random in android is insecure in some old android versions and I have also heard that most android phones have a hard time keeping a high entropy in dev/random and blocks often. Shouldn’t that have a huge impact on the security of dev/urandom?
I am therefore looking for good ways to use the sensors on the phone to gather more entropy.
The following classes should help you alleviate issues with the Android SecureRandom class. This code was created instead of a text because otherwise the small details.
/**
* A strengthener that can be used to generate and re-seed random number
* generators that do not seed themselves appropriately.
*
* #author owlstead
*/
public class SecureRandomStrengthener {
private static final String DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR = "SHA1PRNG";
private static final EntropySource TIME_ENTROPY_SOURCE = new EntropySource() {
final ByteBuffer timeBuffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE
* 2);
#Override
public ByteBuffer provideEntropy() {
this.timeBuffer.clear();
this.timeBuffer.putLong(System.currentTimeMillis());
this.timeBuffer.putLong(System.nanoTime());
this.timeBuffer.flip();
return this.timeBuffer;
}
};
private final String algorithm;
private final List<EntropySource> entropySources = new LinkedList<EntropySource>();
private final MessageDigest digest;
private final ByteBuffer seedBuffer;
/**
* Generates an instance of a {#link SecureRandomStrengthener} that
* generates and re-seeds instances of {#code "SHA1PRNG"}.
*
* #return the strengthener, never null
*/
public static SecureRandomStrengthener getInstance() {
return new SecureRandomStrengthener(
DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR);
}
/**
* Generates an instance of a {#link SecureRandomStrengthener} that
* generates instances of the given argument. Note that the availability of
* the given algorithm arguments in not tested until generation.
*
* #param algorithm
* the algorithm indicating the {#link SecureRandom} instance to
* use
* #return the strengthener, never null
*/
public static SecureRandomStrengthener getInstance(final String algorithm) {
return new SecureRandomStrengthener(algorithm);
}
private SecureRandomStrengthener(final String algorithm) {
if (algorithm == null || algorithm.length() == 0) {
throw new IllegalArgumentException(
"Please provide a PRNG algorithm string such as SHA1PRNG");
}
this.algorithm = algorithm;
try {
this.digest = MessageDigest.getInstance("SHA1");
} catch (final NoSuchAlgorithmException e) {
throw new IllegalStateException(
"MessageDigest to create seed not available", e);
}
this.seedBuffer = ByteBuffer.allocate(this.digest.getDigestLength());
}
/**
* Add an entropy source, which will be called for each generation and
* re-seeding of the given random number generator.
*
* #param source
* the source of entropy
*/
public void addEntropySource(final EntropySource source) {
if (source == null) {
throw new IllegalArgumentException(
"EntropySource should not be null");
}
this.entropySources.add(source);
}
/**
* Generates and seeds a random number generator of the configured
* algorithm. Calls the {#link EntropySource#provideEntropy()} method of all
* added sources of entropy.
*
* #return the random number generator
*/
public SecureRandom generateAndSeedRandomNumberGenerator() {
final SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstance(this.algorithm);
} catch (final NoSuchAlgorithmException e) {
throw new IllegalStateException("PRNG is not available", e);
}
reseed(secureRandom);
return secureRandom;
}
/**
* Re-seeds the random number generator. Calls the
* {#link EntropySource#provideEntropy()} method of all added sources of
* entropy.
*
* #param secureRandom
* the random number generator to re-seed
*/
public void reseed(final SecureRandom secureRandom) {
this.seedBuffer.clear();
secureRandom.nextBytes(this.seedBuffer.array());
for (final EntropySource source : this.entropySources) {
final ByteBuffer entropy = source.provideEntropy();
if (entropy == null) {
continue;
}
final ByteBuffer wipeBuffer = entropy.duplicate();
this.digest.update(entropy);
wipe(wipeBuffer);
}
this.digest.update(TIME_ENTROPY_SOURCE.provideEntropy());
this.digest.update(this.seedBuffer);
this.seedBuffer.clear();
// remove data from seedBuffer so it won't be retrievable
// reuse
try {
this.digest.digest(this.seedBuffer.array(), 0,
this.seedBuffer.capacity());
} catch (final DigestException e) {
throw new IllegalStateException(
"DigestException should not be thrown", e);
}
secureRandom.setSeed(this.seedBuffer.array());
wipe(this.seedBuffer);
}
private void wipe(final ByteBuffer buf) {
while (buf.hasRemaining()) {
buf.put((byte) 0);
}
}
}
And this is the small interface that is EntropySource:
/**
* A simple interface that can be used to retrieve entropy from any source.
*
* #author owlstead
*/
public interface EntropySource {
/**
* Retrieves the entropy.
* The position of the ByteBuffer must be advanced to the limit by any users calling this method.
* The values of the bytes between the position and limit should be set to zero by any users calling this method.
*
* #return entropy within the position and limit of the given buffer
*/
ByteBuffer provideEntropy();
}
Note that the output of the classes has not been tested for randomness (but this relies mainly on the returned SecureRandom class and should therefore be fine).
Finally, as I don't have the Android 1.6 runtime ready, somebody should test it against this or a lower version for compatibility (!).

Android: Is it possible to display video thumbnails?

I created a video recording application with library dialog. The library dialog displays the list of recorded videos where each item consists of icon, video title, tags and location information the following way:
Does anyone know whether it is possible to replace icons with video thumbnails (single frame preview)?
Thanks!
if you don't or cannot go through cursor and if you have only paths or File objects, you can use since API level 8 (2.2)
public static Bitmap createVideoThumbnail (String filePath, int kind)
Android documentation
The following code runs perfectly:
Bitmap bMap = ThumbnailUtils.createVideoThumbnail(file.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
If you are using API 2.0 or newer this will work.
int id = **"The Video's ID"**
ImageView iv = (ImageView ) convertView.findViewById(R.id.imagePreview);
ContentResolver crThumb = getContentResolver();
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap curThumb = MediaStore.Video.Thumbnails.getThumbnail(crThumb, id, MediaStore.Video.Thumbnails.MICRO_KIND, options);
iv.setImageBitmap(curThumb);
Using the class:
import android.provider.MediaStore.Video.Thumbnails;
We can get two preview thumbnail sizes from the video:
Thumbnails.MICRO_KIND for 96 x 96
Thumbnails.MINI_KIND for 512 x 384 px
This is a code example:
String filePath = "/sdcard/DCIM/Camera/my_video.mp4"; //change the location of your file!
ImageView imageview_mini = (ImageView)findViewById(R.id.thumbnail_mini);
ImageView imageview_micro = (ImageView)findViewById(R.id.thumbnail_micro);
Bitmap bmThumbnail;
//MICRO_KIND, size: 96 x 96 thumbnail
bmThumbnail = ThumbnailUtils.createVideoThumbnail(filePath, Thumbnails.MICRO_KIND);
imageview_micro.setImageBitmap(bmThumbnail);
// MINI_KIND, size: 512 x 384 thumbnail
bmThumbnail = ThumbnailUtils.createVideoThumbnail(filePath, Thumbnails.MINI_KIND);
imageview_mini.setImageBitmap(bmThumbnail);
I really suggest you to use the Glide library. It's among the most efficient way to generate and display a video thumbnail for a local video file.
Just add this line to your gradle file :
compile 'com.github.bumptech.glide:glide:3.7.0'
And it will become as simple as :
String filePath = "/storage/emulated/0/Pictures/example_video.mp4";
Glide
.with( context )
.load( Uri.fromFile( new File( filePath ) ) )
.into( imageViewGifAsBitmap );
You can find more informations here : https://futurestud.io/blog/glide-displaying-gifs-and-videos
Cheers !
Currently I Use following code :
Bitmap bMap = ThumbnailUtils.createVideoThumbnail(file.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
But I found better solution with Glide library with following code ( It also cache your image and have better performance than previous approach )
Glide.with(context)
.load(uri)
.placeholder(R.drawable.ic_video_place_holder)
.into(imageView);
Try this it's working for me
RequestOptions requestOptions = new RequestOptions();
Glide.with(getContext())
.load("video_url")
.apply(requestOptions)
.thumbnail(Glide.with(getContext()).load("video_url"))
.into("yourimageview");
This solution will work for any version of Android. It has proven to work in 1.5 and 2.2 This is not another "This is for Android 2.0+" solution. I found this through an email message board collection page and cannot find the original link. All credit goes to the original poster.
In your app you would use this by calling:
Bitmap bm = getVideoFrame(VideoStringUri);
Somewhere in it's own function (outside the OnCreate, ect), you would need:
private Bitmap getVideoFrame(String uri) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
retriever.setDataSource(uri);
return retriever.captureFrame();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (RuntimeException ex) {
ex.printStackTrace();
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
}
}
return null;
}
In your src folder, you need a new subdirectory android/media which will house the class (copied from the android source itself) which allows you to use this function. This part should not be changed, renamed, or placed anywhere else. MediaMetadataRetriever.java needs to be under android.media in your source folder for this all to work.
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
/**
* MediaMetadataRetriever class provides a unified interface for retrieving
* frame and meta data from an input media file. {#hide}
*/
public class MediaMetadataRetriever {
static {
System.loadLibrary("media_jni");
native_init();
}
// The field below is accessed by native methods
private int mNativeContext;
public MediaMetadataRetriever() {
native_setup();
}
/**
* Call this method before setDataSource() so that the mode becomes
* effective for subsequent operations. This method can be called only once
* at the beginning if the intended mode of operation for a
* MediaMetadataRetriever object remains the same for its whole lifetime,
* and thus it is unnecessary to call this method each time setDataSource()
* is called. If this is not never called (which is allowed), by default the
* intended mode of operation is to both capture frame and retrieve meta
* data (i.e., MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY). Often,
* this may not be what one wants, since doing this has negative performance
* impact on execution time of a call to setDataSource(), since both types
* of operations may be time consuming.
*
* #param mode
* The intended mode of operation. Can be any combination of
* MODE_GET_METADATA_ONLY and MODE_CAPTURE_FRAME_ONLY: 1.
* MODE_GET_METADATA_ONLY & MODE_CAPTURE_FRAME_ONLY: For neither
* frame capture nor meta data retrieval 2.
* MODE_GET_METADATA_ONLY: For meta data retrieval only 3.
* MODE_CAPTURE_FRAME_ONLY: For frame capture only 4.
* MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY: For both
* frame capture and meta data retrieval
*/
public native void setMode(int mode);
/**
* #return the current mode of operation. A negative return value indicates
* some runtime error has occurred.
*/
public native int getMode();
/**
* Sets the data source (file pathname) to use. Call this method before the
* rest of the methods in this class. This method may be time-consuming.
*
* #param path
* The path of the input media file.
* #throws IllegalArgumentException
* If the path is invalid.
*/
public native void setDataSource(String path)
throws IllegalArgumentException;
/**
* Sets the data source (FileDescriptor) to use. It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
*
* #param fd
* the FileDescriptor for the file you want to play
* #param offset
* the offset into the file where the data to be played starts,
* in bytes. It must be non-negative
* #param length
* the length in bytes of the data to be played. It must be
* non-negative.
* #throws IllegalArgumentException
* if the arguments are invalid
*/
public native void setDataSource(FileDescriptor fd, long offset, long length)
throws IllegalArgumentException;
/**
* Sets the data source (FileDescriptor) to use. It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
*
* #param fd
* the FileDescriptor for the file you want to play
* #throws IllegalArgumentException
* if the FileDescriptor is invalid
*/
public void setDataSource(FileDescriptor fd)
throws IllegalArgumentException {
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
/**
* Sets the data source as a content Uri. Call this method before the rest
* of the methods in this class. This method may be time-consuming.
*
* #param context
* the Context to use when resolving the Uri
* #param uri
* the Content URI of the data you want to play
* #throws IllegalArgumentException
* if the Uri is invalid
* #throws SecurityException
* if the Uri cannot be used due to lack of permission.
*/
public void setDataSource(Context context, Uri uri)
throws IllegalArgumentException, SecurityException {
if (uri == null) {
throw new IllegalArgumentException();
}
String scheme = uri.getScheme();
if (scheme == null || scheme.equals("file")) {
setDataSource(uri.getPath());
return;
}
AssetFileDescriptor fd = null;
try {
ContentResolver resolver = context.getContentResolver();
try {
fd = resolver.openAssetFileDescriptor(uri, "r");
} catch (FileNotFoundException e) {
throw new IllegalArgumentException();
}
if (fd == null) {
throw new IllegalArgumentException();
}
FileDescriptor descriptor = fd.getFileDescriptor();
if (!descriptor.valid()) {
throw new IllegalArgumentException();
}
// Note: using getDeclaredLength so that our behavior is the same
// as previous versions when the content provider is returning
// a full file.
if (fd.getDeclaredLength() < 0) {
setDataSource(descriptor);
} else {
setDataSource(descriptor, fd.getStartOffset(),
fd.getDeclaredLength());
}
return;
} catch (SecurityException ex) {
} finally {
try {
if (fd != null) {
fd.close();
}
} catch (IOException ioEx) {
}
}
setDataSource(uri.toString());
}
/**
* Call this method after setDataSource(). This method retrieves the meta
* data value associated with the keyCode.
*
* The keyCode currently supported is listed below as METADATA_XXX
* constants. With any other value, it returns a null pointer.
*
* #param keyCode
* One of the constants listed below at the end of the class.
* #return The meta data value associate with the given keyCode on success;
* null on failure.
*/
public native String extractMetadata(int keyCode);
/**
* Call this method after setDataSource(). This method finds a
* representative frame if successful and returns it as a bitmap. This is
* useful for generating a thumbnail for an input media source.
*
* #return A Bitmap containing a representative video frame, which can be
* null, if such a frame cannot be retrieved.
*/
public native Bitmap captureFrame();
/**
* Call this method after setDataSource(). This method finds the optional
* graphic or album art associated (embedded or external url linked) the
* related data source.
*
* #return null if no such graphic is found.
*/
public native byte[] extractAlbumArt();
/**
* Call it when one is done with the object. This method releases the memory
* allocated internally.
*/
public native void release();
private native void native_setup();
private static native void native_init();
private native final void native_finalize();
#Override
protected void finalize() throws Throwable {
try {
native_finalize();
} finally {
super.finalize();
}
}
public static final int MODE_GET_METADATA_ONLY = 0x01;
public static final int MODE_CAPTURE_FRAME_ONLY = 0x02;
/*
* Do not change these values without updating their counterparts in
* include/media/mediametadataretriever.h!
*/
public static final int METADATA_KEY_CD_TRACK_NUMBER = 0;
public static final int METADATA_KEY_ALBUM = 1;
public static final int METADATA_KEY_ARTIST = 2;
public static final int METADATA_KEY_AUTHOR = 3;
public static final int METADATA_KEY_COMPOSER = 4;
public static final int METADATA_KEY_DATE = 5;
public static final int METADATA_KEY_GENRE = 6;
public static final int METADATA_KEY_TITLE = 7;
public static final int METADATA_KEY_YEAR = 8;
public static final int METADATA_KEY_DURATION = 9;
public static final int METADATA_KEY_NUM_TRACKS = 10;
public static final int METADATA_KEY_IS_DRM_CRIPPLED = 11;
public static final int METADATA_KEY_CODEC = 12;
public static final int METADATA_KEY_RATING = 13;
public static final int METADATA_KEY_COMMENT = 14;
public static final int METADATA_KEY_COPYRIGHT = 15;
public static final int METADATA_KEY_BIT_RATE = 16;
public static final int METADATA_KEY_FRAME_RATE = 17;
public static final int METADATA_KEY_VIDEO_FORMAT = 18;
public static final int METADATA_KEY_VIDEO_HEIGHT = 19;
public static final int METADATA_KEY_VIDEO_WIDTH = 20;
public static final int METADATA_KEY_WRITER = 21;
public static final int METADATA_KEY_MIMETYPE = 22;
public static final int METADATA_KEY_DISCNUMBER = 23;
public static final int METADATA_KEY_ALBUMARTIST = 24;
// Add more here...
}
Android 1.5 and 1.6 do not offer this thumbnails, but 2.0 does, as seen on the official release notes:
Media
MediaScanner now generates thumbnails for all images when they are inserted into MediaStore.
New Thumbnail API for retrieving image and video thumbnails on demand.
I am answering this question late but hope it will help the other candidate facing same problem.
I have used two methods to load thumbnail for videos list the first was
Bitmap bmThumbnail;
bmThumbnail = ThumbnailUtils.createVideoThumbnail(FILE_PATH
+ videoList.get(position),
MediaStore.Video.Thumbnails.MINI_KIND);
if (bmThumbnail != null) {
Log.d("VideoAdapter","video thumbnail found");
holder.imgVideo.setImageBitmap(bmThumbnail);
} else {
Log.d("VideoAdapter","video thumbnail not found");
}
its look good but there was a problem with this solution because when i scroll video list it will freeze some time due to its large processing.
so after this i found another solution which works perfectly by using Glide Library.
Glide
.with( mContext )
.load( Uri.fromFile( new File( FILE_PATH+videoList.get(position) ) ) )
.into( holder.imgVideo );
I recommended the later solution for showing thumbnail with video list .
thanks
I know this is an old question with an accepted answer but I want to post my answer in case somebody is searching for this:
so when I wanted to only use Uri so I did this:
val mmr = MediaMetadataRetriever()
mmr.setDataSource(videoUri)
val thummbnailBitmap = mmr.frameAtTime
imageView.setImageBitmap(thummbnailBitmap)
This is code for live Video thumbnail.
public class LoadVideoThumbnail extends AsyncTask<Object, Object, Bitmap>{
#Override
protected Bitmap doInBackground(Object... params) {try {
String mMediaPath = "http://commonsware.com/misc/test2.3gp";
Log.e("TEST Chirag","<< thumbnail doInBackground"+ mMediaPath);
FileOutputStream out;
File land=new File(Environment.getExternalStorageDirectory().getAbsoluteFile()
+"/portland.jpg");
Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(mMediaPath, MediaStore.Video.Thumbnails.MICRO_KIND);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
out=new FileOutputStream(land.getPath());
out.write(byteArray);
out.close();
return bitmap;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Bitmap result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
if(result != null){
((ImageView)findViewById(R.id.imageView1)).setImageBitmap(result);
}
Log.e("TEST Chirag","====> End");
}
}

Categories

Resources