Android3?


Primary External
Storage. This directory and its contents will be deleted when the app is uninstalled.
2. Environment.getExternalStoragePublicDirectory() for centralized places to store well-known file types, like
photos and movies. This directory and its contents will NOT be deleted when the app is uninstalled.
Methods to store in Removable Storage(GT) i.e. micro SD card
Before API level 19, there was no official way to store in SD card. But, many could do it using unofficial libraries or
APIs.
Officially, one method was introduced in Context class in API level 19 (Android version 4.4 - Kitkat).
File[] getExternalFilesDirs (String type)
GoalKicker.com – Android™ Notes for Professionals 386
It returns absolute paths to application-specific directories on all shared/external storage devices where
the application can place persistent files it owns. These files are internal to the application, and not
typically visible to the user as media.
That means, it will return paths to both types of External Storage(GT) - Internal memory and Micro SD card.
Generally second path would be storage path of micro SD card(but not always). So you need to check it out by
executing the code with this method.
Example with code snippet:
I created a new android project with empty activity, wrote the following code inside
protected void onCreate(Bundle savedInstanceState) method of MainActivity.java
File internal_m1 = getDir("custom", 0);
File internal_m2 = getFilesDir();
File external_m1 = Environment.getExternalStorageDirectory();
File external_m2 = getExternalFilesDir(null);
File external_m2_Args = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File external_m3 =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File[] external_AND_removable_storage_m1 = getExternalFilesDirs(null);
File[] external_AND_removable_storage_m1_Args =
getExternalFilesDirs(Environment.DIRECTORY_PICTURES);
After executing above code,
Output:
internal_m1: /data/data/your.application.package.appname/app_custom
internal_m2: /data/data/your.application.package.appname/files
external_m1: /storage/emulated/0
external_m2: /storage/emulated/0/Android/data/your.application.package.appname/files
external_m2_Args: /storage/emulated/0/Android/data/your.application.package.appname/files/Pictures
external_m3: /storage/emulated/0/Pictures
external_AND_removable_storage_m1 (first path):
/storage/emulated/0/Android/data/your.application.package.appname/files
external_AND_removable_storage_m1 (second path):
/storage/sdcard1/Android/data/your.application.package.appname/files
external_AND_removable_storage_m1_Args (first path):
/storage/emulated/0/Android/data/your.application.package.appname/files/Pictures
external_AND_removable_storage_m1_Args (second path):
/storage/sdcard1/Android/data/your.application.package.appname/files/Pictures
Note: I have connected my phone to Windows PC; enabled both developer options, USB debugging and then ran
GoalKicker.com – Android™ Notes for Professionals 387
this code. If you do not connect your phone; but instead run this on Android emulator, your output may vary. My
phone model is Coolpad Note 3 - running on Android 5.1
Storage locations on my phone:
Micro SD storage location: /storage/sdcard1
Internal Storage(UT) location: /storage/sdcard0.
Note that /sdcard & /storage/emulated/0 also point to Internal Storage(UT). But these are symlinks to
/storage/sdcard0.
To clearly understand different storage paths in Android, Please go through this answer
Disclaimer: All the storage paths mentioned above are paths on my phone. Your files may not be stored on same
storage paths. Because, the storage locations/paths may vary on other mobile phones depending on your vendor,
manufacturer and different versions of Android OS.
Section 50.2: Using External Storage
"External" Storage is another type of storage that we can use to save files to the user's device. It has some key
differences from "Internal" Storage, namely:
It is not always available. In the case of a removable medium (SD card), the user can simply remove the
storage.
It is not private. The user (and other applications) have access to these files.
If the user uninstalls the app, the files you save in the directory retrieved with getExternalFilesDir() will be
removed.
To use External Storage, we need to first obtain the proper permissions. You will need to use:
android.permission.WRITE_EXTERNAL_STORAGE for reading and writing
android.permission.READ_EXTERNAL_STORAGE for just reading
To grant these permissions, you will need to identify them in your AndroidManifest.xml as such
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
NOTE: Since they are Dangerous permissions if you are using API Level 23 or above, you will need to
request the permissions at runtime.
Before attempting to write or read from External Storage, you should always check that the storage medium is
available.
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
// Available to read and write
}
if (state.equals(Environment.MEDIA_MOUNTED) ||
state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
// Available to at least read
}
When writing files to the External Storage, you should decide if the file should be recognized as Public or Private.
GoalKicker.com – Android™ Notes for Professionals 388
While both of these types of files are still accessible to the user and other applications on the device, there is a key
distinction between them.
Public files should remain on the device when the user uninstalls the app. An example of a file that should be saved
as Public would be photos that are taken through your application.
Private files should all be removed when the user uninstalls the app. These types of files would be app specific, and
not be of use to the user or other applications. Ex. temporary files downloaded/used by your application.
Here's how to get access to the Documents directory for both Public and Private files.
Public
// Access your app's directory in the device's Public documents directory
File docs = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS), "YourAppDirectory");
// Make the directory if it does not yet exist
myDocs.mkdirs();
Private
// Access your app's Private documents directory
File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS),
"YourAppDirectory");
// Make the directory if it does not yet exist
myDocs.mkdirs();
Section 50.3: Using Internal Storage
By default, any files that you save to Internal Storage are private to your application. They cannot be accessed by
other applications, nor the user under normal circumstances. These files are deleted when the user uninstalls
the application.
To Write Text to a File
String fileName= "helloworld";
String textToWrite = "Hello, World!";
FileOutputStream fileOutputStream;
try {
fileOutputStream = openFileOutput(fileName, Context.MODE_PRIVATE);
fileOutputStream.write(textToWrite.getBytes());
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
To Append Text to an Existing File
Use Context.MODE_APPEND for the mode parameter of openFileOutput
fileOutputStream = openFileOutput(fileName, Context.MODE_APPEND);
Section 50.4: Fetch Device Directory :
First Add Storage permission to read/fetch device directory.
GoalKicker.com – Android™ Notes for Professionals 389
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Create model class
//create one directory model class
//to store directory title and type in list
public class DirectoryModel {
String dirName;
int dirType; // set 1 or 0, where 0 for directory and 1 for file.
public int getDirType() {
return dirType;
}
public void setDirType(int dirType) {
this.dirType = dirType;
}
public String getDirName() {
return dirName;
}
public void setDirName(String dirName) {
this.dirName = dirName;
}
}
Create list using directory model to add directory data.
//define list to show directory
List<DirectoryModel> rootDir = new ArrayList<>();
Fetch directory using following method.
//to fetch device directory
private void getDirectory(String currDir) { // pass device root directory
File f = new File(currDir);
File[] files = f.listFiles();
if (files != null) {
if (files.length > 0) {
rootDir.clear();
for (File inFile : files) {
if (inFile.isDirectory()) { //return true if it's directory
// is directory
DirectoryModel dir = new DirectoryModel();
dir.setDirName(inFile.toString().replace("/storage/emulated/0", ""));
dir.setDirType(0); // set 0 for directory
rootDir.add(dir);
} else if (inFile.isFile()) { // return true if it's file
//is file
DirectoryModel dir = new DirectoryModel();
dir.setDirName(inFile.toString().replace("/storage/emulated/0", ""));
dir.setDirType(1); // set 1 for file
rootDir.add(dir);
}
}
GoalKicker.com – Android™ Notes for Professionals 390
}
printDirectoryList();
}
}
Print directory list in log.
//print directory list in logs
private void printDirectoryList() {
for (int i = 0; i < rootDir.size(); i++) {
Log.e(TAG, "printDirectoryLogs: " + rootDir.get(i).toString());
}
}
Usage
//to Fetch Directory Call function with root directory.
String rootPath = Environment.getExternalStorageDirectory().toString(); // return ==>
/storage/emulated/0/
getDirectory(rootPath );
To fetch inner files/folder of specific directory use same method just change argument, pass the current
selected path in argument and handle response for same.
To get File Extension :
private String getExtension(String filename) {
String filenameArray[] = filename.split("\\.");
String extension = filenameArray[filenameArray.length - 1];
Log.d(TAG, "getExtension: " + extension);
return extension;
}
Section 50.5: Save Database on SD Card (Backup DB on SD)
public static Boolean ExportDB(String DATABASE_NAME , String packageName , String folderName){
//DATABASE_NAME including ".db" at the end like "mayApp.db"
String DBName = DATABASE_NAME.substring(0, DATABASE_NAME.length() - 3);
File data = Environment.getDataDirectory();
FileChannel source=null;
FileChannel destination=null;
String currentDBPath = "/data/"+ packageName +"/databases/"+DATABASE_NAME; // getting app db
path
File sd = Environment.getExternalStorageDirectory(); // getting phone SD card path
String backupPath = sd.getAbsolutePath() + folderName; // if you want to set backup in specific
folder name
/* be careful , foldername must initial like this : "/myFolder" . don't forget "/" at begin
of folder name
you could define foldername like this : "/myOutterFolder/MyInnerFolder" and so on ...
*/
File dir = new File(backupPath);
if(!dir.exists()) // if there was no folder at this path , it create it .
{
dir.mkdirs();
GoalKicker.com – Android™ Notes for Professionals 391
}
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
Date date = new Date();
/* use date including file name for arrange them and preventing to make file with the same*/
File currentDB = new File(data, currentDBPath);
File backupDB = new File(backupPath, DBName +"("+ dateFormat.format(date)+").db");
try {
if (currentDB.exists() && !backupDB.exists()) {
source = new FileInputStream(currentDB).getChannel();
destination = new FileOutputStream(backupDB).getChannel();
destination.transferFrom(source, 0, source.size());
source.close();
destination.close();
return true;
}
return false;
} catch(IOException e) {
e.printStackTrace();
return false;
}
}
call this method this way :
ExportDB("myDB.db","com.example.exam","/myFolder");
GoalKicker.com – Android™ Notes for Professionals 392
Chapter 51: Zip file in android
Section 51.1: Zip file on android
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Compress {
private static final int BUFFER = 2048;
private String[] _files;
private String _zipFile;
public Compress(String[] files, String zipFile) {
_files = files;
_zipFile = zipFile;
}
public void zip() {
try {
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream(_zipFile);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
byte data[] = new byte[BUFFER];
for(int i=0; i < _files.length; i++) {
Log.v("Compress", "Adding: " + _files[i]);
FileInputStream fi = new FileInputStream(_files[i]);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(_files[i].substring(_files[i].lastIndexOf("/") + 1));
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
out.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
GoalKicker.com – Android™ Notes for Professionals 393
Chapter 52: Unzip File in Android
Section 52.1: Unzip file
private boolean unpackZip(String path, String zipname){
InputStream is;
ZipInputStream zis;
try
{
String filename;
is = new FileInputStream(path + zipname);
zis = new ZipInputStream(new BufferedInputStream(is));
ZipEntry ze;
byte[] buffer = new byte[1024];
int count;
while ((ze = zis.getNextEntry()) != null){
// zapis do souboru
filename = ze.getName();
// Need to create directories if not exists, or
// it will generate an Exception...
if (ze.isDirectory()) {
File fmd = new File(path + filename);
fmd.mkdirs();
continue;
}
FileOutputStream fout = new FileOutputStream(path + filename);
// cteni zipu a zapis
while ((count = zis.read(buffer)) != -1){
fout.write(buffer, 0, count);
}
fout.close();
zis.closeEntry();
}
zis.close();
}
catch(IOException e){
e.printStackTrace();
return false;
}
return true;}
GoalKicker.com – Android™ Notes for Professionals 394
Chapter 53: Camera and Gallery
Section 53.1: Take photo
Add a permission to access the camera to the AndroidManifest file:
<uses-permission android:name="android.permission.CAMERA"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Xml file :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<SurfaceView android:id="@+id/surfaceView" android:layout_height="0dip"
android:layout_width="0dip"></SurfaceView>
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/imageView"></ImageView>
</LinearLayout>
Activity
import java.io.IOException;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.ImageView;
public class TakePicture extends Activity implements SurfaceHolder.Callback
{
//a variable to store a reference to the Image View at the main.xml file
private ImageView iv_image;
//a variable to store a reference to the Surface View at the main.xml file
private SurfaceView sv;
//a bitmap to display the captured image
private Bitmap bmp;
//Camera variables
//a surface holder
private SurfaceHolder sHolder;
//a variable to control the camera
private Camera mCamera;
//the camera parameters
private Parameters parameters;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
GoalKicker.com – Android™ Notes for Professionals 395
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//get the Image View at the main.xml file
iv_image = (ImageView) findViewById(R.id.imageView);
//get the Surface View at the main.xml file
sv = (SurfaceView) findViewById(R.id.surfaceView);
//Get a surface
sHolder = sv.getHolder();
//add the callback interface methods defined below as the Surface View callbacks
sHolder.addCallback(this);
//tells Android that this surface will have its data constantly replaced
sHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
{
//get camera parameters
parameters = mCamera.getParameters();
//set camera parameters
mCamera.setParameters(parameters);
mCamera.startPreview();
//sets what code should be executed after the picture is taken
Camera.PictureCallback mCall = new Camera.PictureCallback()
{
@Override
public void onPictureTaken(byte[] data, Camera camera)
{
//decode the data obtained by the camera into a Bitmap
bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
String filename=Environment.getExternalStorageDirectory()
+ File.separator + "testimage.jpg";
FileOutputStream out = null;
try {
out = new FileOutputStream(filename);
bmp.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap
instance
// PNG is a lossless format, the compression factor (100) is ignored
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//set the iv_image
iv_image.setImageBitmap(bmp);
}
};
mCamera.takePicture(null, null, mCall);
GoalKicker.com – Android™ Notes for Professionals 396
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
// The Surface has been created, acquire the camera and tell it where
// to draw the preview.
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException exception) {
mCamera.release();
mCamera = null;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
//stop the preview
mCamera.stopPreview();
//release the camera
mCamera.release();
//unbind the camera from this object
mCamera = null;
}
}
Section 53.2: Taking full-sized photo from camera
To take a photo, first we need to declare required permissions in AndroidManifest.xml. We need two permissions:
Camera - to open camera app. If attribute required is set to true you will not be able to install this app if you
don't have hardware camera.
WRITE_EXTERNAL_STORAGE - This permission is required to create new file, in which captured photo will be
saved.
AndroidManifest.xml
<uses-feature android:name="android.hardware.camera"
android:required="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
The main idea in taking full-sized photo from camera is that we need to create new file for photo, before we open
camera app and capture photo.
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Log.e("DEBUG_TAG", "createFile", ex);
}
// Continue only if the File was successfully created
if (photoFile != null) {
GoalKicker.com – Android™ Notes for Professionals 397
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new
Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getAlbumDir();
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
private File getAlbumDir() {
File storageDir = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
storageDir = new File(Environment.getExternalStorageDirectory()
+ "/dcim/"
+ "MyRecipes");
if (!storageDir.mkdirs()) {
if (!storageDir.exists()) {
Log.d("CameraSample", "failed to create directory");
return null;
}
}
} else {
Log.v(getString(R.string.app_name), "External storage is not mounted READ/WRITE.");
}
return storageDir;
}
private void setPic() {
/* There isn't enough memory to open up more than a couple camera photos */
/* So pre-scale the target bitmap into which the file is decoded */
/* Get the size of the ImageView */
int targetW = recipeImage.getWidth();
int targetH = recipeImage.getHeight();
/* Get the size of the image */
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
GoalKicker.com – Android™ Notes for Professionals 398
/* Figure out which way needs to be reduced less */
int scaleFactor = 2;
if ((targetW > 0) && (targetH > 0)) {
scaleFactor = Math.max(photoW / targetW, photoH / targetH);
}
/* Set bitmap options to scale the image decode target */
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Matrix matrix = new Matrix();
matrix.postRotate(getRotation());
/* Decode the JPEG file into a Bitmap */
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,
false);
/* Associate the Bitmap to the ImageView */
recipeImage.setImageBitmap(bitmap);
}
private float getRotation() {
try {
ExifInterface ei = new ExifInterface(mCurrentPhotoPath);
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90f;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180f;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270f;
default:
return 0f;
}
} catch (Exception e) {
Log.e("Add Recipe", "getRotation", e);
return 0f;
}
}
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}
private void handleBigCameraPhoto() {
if (mCurrentPhotoPath != null) {
setPic();
galleryAddPic();
}
}
GoalKicker.com – Android™ Notes for Professionals 399
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
handleBigCameraPhoto();
}
}
Section 53.3: Decode bitmap correctly rotated from the uri
fetched with the intent
private static final String TAG = "IntentBitmapFetch";
private static final String COLON_SEPARATOR = ":";
private static final String IMAGE = "image";
@Nullable
public Bitmap getBitmap(@NonNull Uri bitmapUri, int maxDimen) {
InputStream is = context.getContentResolver().openInputStream(bitmapUri);
Bitmap bitmap = BitmapFactory.decodeStream(is, null, getBitmapOptions(bitmapUri, maxDimen));
int imgRotation = getImageRotationDegrees(bitmapUri);
int endRotation = (imgRotation < 0) ? -imgRotation : imgRotation;
endRotation %= 360;
endRotation = 90 * (endRotation / 90);
if (endRotation > 0 && bitmap != null) {
Matrix m = new Matrix();
m.setRotate(endRotation);
Bitmap tmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m,
true);
if (tmp != null) {
bitmap.recycle();
bitmap = tmp;
}
}
return bitmap;
}
private BitmapFactory.Options getBitmapOptions(Uri uri, int imageMaxDimen){
BitmapFactory.Options options = new BitmapFactory.Options();
if (imageMaxDimen > 0) {
options.inJustDecodeBounds = true;
decodeImage(null, uri, options);
options.inSampleSize = calculateScaleFactor(options, imageMaxDimen);
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
addInBitmapOptions(options);
}
}
private int calculateScaleFactor(@NonNull BitmapFactory.Options bitmapOptionsMeasureOnly, int
imageMaxDimen) {
int inSampleSize = 1;
if (bitmapOptionsMeasureOnly.outHeight > imageMaxDimen || bitmapOptionsMeasureOnly.outWidth >
imageMaxDimen) {
final int halfHeight = bitmapOptionsMeasureOnly.outHeight / 2;
final int halfWidth = bitmapOptionsMeasureOnly.outWidth / 2;
while ((halfHeight / inSampleSize) > imageMaxDimen && (halfWidth / inSampleSize) >
imageMaxDimen) {
inSampleSize *= 2;
GoalKicker.com – Android™ Notes for Professionals 400
}
}
return inSampleSize;
}
public int getImageRotationDegrees(@NonNull Uri imgUri) {
int photoRotation = ExifInterface.ORIENTATION_UNDEFINED;
try {
boolean hasRotation = false;
//If image comes from the gallery and is not in the folder DCIM (Scheme: content://)
String[] projection = {MediaStore.Images.ImageColumns.ORIENTATION};
Cursor cursor = context.getContentResolver().query(imgUri, projection, null, null, null);
if (cursor != null) {
if (cursor.getColumnCount() > 0 && cursor.moveToFirst()) {
photoRotation = cursor.getInt(cursor.getColumnIndex(projection[0]));
hasRotation = photoRotation != 0;
Log.d("Cursor orientation: "+ photoRotation);
}
cursor.close();
}
//If image comes from the camera (Scheme: file://) or is from the folder DCIM (Scheme:
content://)
if (!hasRotation) {
ExifInterface exif = new ExifInterface(getAbsolutePath(imgUri));
int exifRotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (exifRotation) {
case ExifInterface.ORIENTATION_ROTATE_90: {
photoRotation = 90;
break;
}
case ExifInterface.ORIENTATION_ROTATE_180: {
photoRotation = 180;
break;
}
case ExifInterface.ORIENTATION_ROTATE_270: {
photoRotation = 270;
break;
}
}
Log.d(TAG, "Exif orientation: "+ photoRotation);
}
} catch (IOException e) {
Log.e(TAG, "Error determining rotation for image"+ imgUri, e);
}
return photoRotation;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private String getAbsolutePath(Uri uri) {
//Code snippet edited from: http://stackoverflow.com/a/20559418/2235133
String filePath = uri.getPath();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
DocumentsContract.isDocumentUri(context, uri)) {
// Will return "image:x*"
String[] wholeID = TextUtils.split(DocumentsContract.getDocumentId(uri), COLON_SEPARATOR);
// Split at colon, use second item in the array
String type = wholeID[0];
if (IMAGE.equalsIgnoreCase(type)) {//If it not type image, it means it comes from a remote
location, like Google Photos
GoalKicker.com – Android™ Notes for Professionals 401
String id = wholeID[1];
String[] column = {MediaStore.Images.Media.DATA};
// where id is equal to
String sel = MediaStore.Images.Media._ID + "=?";
Cursor cursor = context.getContentResolver().
query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
column, sel, new String[]{id}, null);
if (cursor != null) {
int columnIndex = cursor.getColumnIndex(column[0]);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
}
cursor.close();
}
Log.d(TAG, "Fetched absolute path for uri" + uri);
}
}
return filePath;
}
Section 53.4: Set camera resolution
Set High resolution programmatically.
Camera mCamera = Camera.open();
Camera.Parameters params = mCamera.getParameters();
// Check what resolutions are supported by your camera
List<Size> sizes = params.getSupportedPictureSizes();
// Iterate through all available resolutions and choose one.
// The chosen resolution will be stored in mSize.
Size mSize;
for (Size size : sizes) {
Log.i(TAG, "Available resolution: "+size.width+" "+size.height);
mSize = size;
}
}
Log.i(TAG, "Chosen resolution: "+mSize.width+" "+mSize.height);
params.setPictureSize(mSize.width, mSize.height);
mCamera.setParameters(params);
Section 53.5: How to start camera or gallery and save
camera result to storage
First of all you need Uri and temp Folders and request codes :
public final int REQUEST_SELECT_PICTURE = 0x01;
public final int REQUEST_CODE_TAKE_PICTURE = 0x2;
public static String TEMP_PHOTO_FILE_NAME ="photo_";
Uri mImageCaptureUri;
File mFileTemp;
Then init mFileTemp :
public void initTempFile(){
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
GoalKicker.com – Android™ Notes for Professionals 402
mFileTemp = new File(Environment.getExternalStorageDirectory() + File.separator
+ getResources().getString(R.string.app_foldername) + File.separator
+ getResources().getString(R.string.pictures_folder)
, TEMP_PHOTO_FILE_NAME
+ System.currentTimeMillis() + ".jpg");
mFileTemp.getParentFile().mkdirs();
} else {
mFileTemp = new File(getFilesDir() + File.separator
+ getResources().getString(R.string.app_foldername)
+ File.separator + getResources().getString(R.string.pictures_folder)
, TEMP_PHOTO_FILE_NAME + System.currentTimeMillis() + ".jpg");
mFileTemp.getParentFile().mkdirs();
}
}
Opening Camera and Gallery intents :
public void openCamera(){
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try {
mImageCaptureUri = null;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
mImageCaptureUri = Uri.fromFile(mFileTemp);
} else {
mImageCaptureUri = InternalStorageContentProvider.CONTENT_URI;
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
intent.putExtra("return-data", true);
startActivityForResult(intent, REQUEST_CODE_TAKE_PICTURE);
} catch (Exception e) {
Log.d("error", "cannot take picture", e);
}
}
public void openGallery(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
requestPermission(Manifest.permission.READ_EXTERNAL_STORAGE,
getString(R.string.permission_read_storage_rationale),
REQUEST_STORAGE_READ_ACCESS_PERMISSION);
} else {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
REQUEST_SELECT_PICTURE);
}
}
Then in onActivityResult method :
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
GoalKicker.com – Android™ Notes for Professionals 403
if (resultCode != RESULT_OK) {
return;
}
Bitmap bitmap;
switch (requestCode) {
case REQUEST_SELECT_PICTURE:
try {
Uri uri = data.getData();
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
Bitmap bitmapScaled = Bitmap.createScaledBitmap(bitmap, 800, 800, true);
Drawable drawable=new BitmapDrawable(bitmapScaled);
mImage.setImageDrawable(drawable);
mImage.setVisibility(View.VISIBLE);
} catch (IOException e) {
Log.v("act result", "there is an error : "+e.getContent());
}
} catch (Exception e) {
Log.v("act result", "there is an error : "+e.getContent());
}
break;
case REQUEST_CODE_TAKE_PICTURE:
try{
Bitmap bitmappicture = MediaStore.Images.Media.getBitmap(getContentResolver() ,
mImageCaptureUri);
mImage.setImageBitmap(bitmappicture);
mImage.setVisibility(View.VISIBLE);
}catch (IOException e){
Log.v("error camera",e.getMessage());
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
You need theese permissions in AndroidManifest.xml :
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
And you need to handle runtime permissions such as Read/Write external storage etc ...
I am checking READ_EXTERNAL_STORAGE permission in my openGallery method :
My requestPermission method :
protected void requestPermission(final String permission, String rationale, final int requestCode)
{
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
showAlertDialog(getString(R.string.permission_title_rationale), rationale,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(BasePermissionActivity.this,
new String[]{permission}, requestCode);
}
}, getString(android.R.string.ok), null, getString(android.R.string.cancel));
} else {
GoalKicker.com – Android™ Notes for Professionals 404
ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
}
}
Then Override onRequestPermissionsResult method :
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull
int[] grantResults) {
switch (requestCode) {
case REQUEST_STORAGE_READ_ACCESS_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
handleGallery();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
showAlertDialog method :
protected void showAlertDialog(@Nullable String title, @Nullable String message,
@Nullable DialogInterface.OnClickListener
onPositiveButtonClickListener,
@NonNull String positiveText,
@Nullable DialogInterface.OnClickListener
onNegativeButtonClickListener,
@NonNull String negativeText) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton(positiveText, onPositiveButtonClickListener);
builder.setNegativeButton(negativeText, onNegativeButtonClickListener);
mAlertDialog = builder.show();
}
GoalKicker.com – Android™ Notes for Professionals 405
Chapter 54: Camera 2 API
Parameter Details
CameraCaptureSession A configured capture session for a CameraDevice, used for capturing images from the
camera or reprocessing images captured from the camera in the same session previously
CameraDevice A representation of a single camera connected to an Android device
CameraCharacteristics
The properties describing a CameraDevice. These properties are fixed for a given
CameraDevice, and can be queried through the CameraManager interface with
getCameraCharacteristics(String)
CameraManager A system service manager for detecting, characterizing, and connecting to CameraDevices.
You can get an instance of this class by calling Context.getSystemService()
CaptureRequest
An immutable package of settings and outputs needed to capture a single image from the
camera device. Contains the configuration for the capture hardware (sensor, lens, flash),
the processing pipeline, the control algorithms, and the output buffers. Also contains the
list of target Surfaces to send image data to for this capture. Can be created by using a
CaptureRequest.Builder instance, obtained by calling createCaptureRequest(int)
CaptureResult
The subset of the results of a single image capture from the image sensor. Contains a
subset of the final configuration for the capture hardware (sensor, lens, flash), the
processing pipeline, the control algorithms, and the output buffers. It is produced by a
CameraDevice after processing a CaptureRequest
Section 54.1: Preview the main camera in a TextureView
In this case, building against API 23, so permissions are handled too.
You must add in the Manifest the following permission (wherever the API level you're using):
<uses-permission android:name="android.permission.CAMERA"/>
We're about to create an activity (Camera2Activity.java) that fills a TextureView with the preview of the device's
camera.
The Activity we're going to use is a typical AppCompatActivity:
public class Camera2Activity extends AppCompatActivity {
Attributes (You may need to read the entire example to understand some of it)
The MAX_PREVIEW_SIZE guaranteed by Camera2 API is 1920x1080
private static final int MAX_PREVIEW_WIDTH = 1920;
private static final int MAX_PREVIEW_HEIGHT = 1080;
TextureView.SurfaceTextureListener handles several lifecycle events on a TextureView. In this case, we're
listening to those events. When the SurfaceTexture is ready, we initialize the camera. When it size changes, we
setup the preview coming from the camera accordingly
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
GoalKicker.com – Android™ Notes for Professionals 406
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
A CameraDevice represent one physical device's camera. In this attribute, we save the ID of the current
CameraDevice
private String mCameraId;
This is the view (TextureView) that we'll be using to "draw" the preview of the Camera
private TextureView mTextureView;
The CameraCaptureSession for camera preview
private CameraCaptureSession mCaptureSession;
A reference to the opened CameraDevice
private CameraDevice mCameraDevice;
The Size of camera preview.
private Size mPreviewSize;
CameraDevice.StateCallback is called when CameraDevice changes its state
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
GoalKicker.com – Android™ Notes for Professionals 407
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
finish();
}
};
An additional thread for running tasks that shouldn't block the UI
private HandlerThread mBackgroundThread;
A Handler for running tasks in the background
private Handler mBackgroundHandler;
An ImageReader that handles still image capture
private ImageReader mImageReader;
CaptureRequest.Builder for the camera preview
private CaptureRequest.Builder mPreviewRequestBuilder;
CaptureRequest generated by mPreviewRequestBuilder
private CaptureRequest mPreviewRequest;
A Semaphore to prevent the app from exiting before closing the camera.
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
Constant ID of the permission request
private static final int REQUEST_CAMERA_PERMISSION = 1;
Android Lifecycle methods
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
mTextureView = (TextureView) findViewById(R.id.texture);
}
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
GoalKicker.com – Android™ Notes for Professionals 408
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
Camera2 related methods
Those are methods that uses the Camera2 APIs
private void openCamera(int width, int height) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
}
setUpCameraOutputs(width, height);
configureTransform(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
Closes the current camera
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
GoalKicker.com – Android™ Notes for Professionals 409
Sets up member variables related to camera
private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample.
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// For still image captures, we use the largest available size.
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(
null, mBackgroundHandler);
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
// Danger! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
mCameraId = cameraId;
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
Toast.makeText(Camera2Activity.this, "Camera2 API not supported on this device",
Toast.LENGTH_LONG).show();
GoalKicker.com – Android™ Notes for Professionals 410
}
}
Creates a new CameraCaptureSession for camera preview
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
// Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("Failed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
Permissions related methods For Android API 23+
GoalKicker.com – Android™ Notes for Professionals 411
private void requestCameraPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
new AlertDialog.Builder(Camera2Activity.this)
.setMessage("R string request permission")
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(Camera2Activity.this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.create();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(Camera2Activity.this, "ERROR: Camera permissions not granted",
Toast.LENGTH_LONG).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
Background thread / handler methods
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Utility methods
GoalKicker.com – Android™ Notes for Professionals 412
Given choices of Sizes supported by a camera, choose the smallest one that is at least at large as the respective
texture view size, and that is as most as large as the respective max size, and whose aspect ratio matches with the
specified value. If doesn't exist, choose the largest one that is at most as large as the respective max size, and
whose aspect ratio matches with the specified value
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size
aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e("Camera2", "Couldn't find any suitable preview size");
return choices[0];
}
}
This method congfigures the neccesary Matrix transformation to mTextureView
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mTextureView || null == mPreviewSize) {
return;
}
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
GoalKicker.com – Android™ Notes for Professionals 413
matrix.postRotate(180, centerX, centerY);
}
mTextureView.setTransform(matrix);
}
This method compares two Sizes based on their areas.
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
Not much to see here
/**
* Shows a {@link Toast} on the UI thread.
*
* @param text The message to show
*/
private void showToast(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show();
}
});
}
GoalKicker.com – Android™ Notes for Professionals 414
Chapter 55: Fingerprint API in android
Section 55.1: How to use Android Fingerprint API to save user
passwords
This example helper class interacts with the finger print manager and performs encryption and decryption of
password. Please note that the method used for encryption in this example is AES. This is not the only way to
encrypt and other examples exist. In this example the data is encrypted and decrypted in the following manner:
Encryption:
1. User gives helper the desired non-encrypted password.
2. User is required to provide fingerprint.
3. Once authenticated, the helper obtains a key from the KeyStore and encrypts the password using a Cipher.
4. Password and IV salt (IV is recreated for every encryption and is not reused) are saved to shared preferences
to be used later in the decryption process.
Decryption:
1. User requests to decrypt the password.
2. User is required to provide fingerprint.
3. The helper builds a Cipher using the IV and once user is authenticated, the KeyStore obtains a key from the
KeyStore and deciphers the password.
public class FingerPrintAuthHelper {
private static final String FINGER_PRINT_HELPER = "FingerPrintAuthHelper";
private static final String ENCRYPTED_PASS_SHARED_PREF_KEY = "ENCRYPTED_PASS_SHARED_PREF_KEY";
private static final String LAST_USED_IV_SHARED_PREF_KEY = "LAST_USED_IV_SHARED_PREF_KEY";
private static final String MY_APP_ALIAS = "MY_APP_ALIAS";
private KeyguardManager keyguardManager;
private FingerprintManager fingerprintManager;
private final Context context;
private KeyStore keyStore;
private KeyGenerator keyGenerator;
private String lastError;
public interface Callback {
void onSuccess(String savedPass);
void onFailure(String message);
void onHelp(int helpCode, String helpString);
}
public FingerPrintAuthHelper(Context context) {
this.context = context;
}
public String getLastError() {
return lastError;
}
GoalKicker.com – Android™ Notes for Professionals 415
@TargetApi(Build.VERSION_CODES.M)
public boolean init() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
setError("This Android version does not support fingerprint authentication");
return false;
}
keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
fingerprintManager = (FingerprintManager) context.getSystemService(FINGERPRINT_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {
setError("User hasn't enabled Lock Screen");
return false;
}
if (!hasPermission()) {
setError("User hasn't granted permission to use Fingerprint");
return false;
}
if (!fingerprintManager.hasEnrolledFingerprints()) {
setError("User hasn't registered any fingerprints");
return false;
}
if (!initKeyStore()) {
return false;
}
return false;
}
@Nullable
@RequiresApi(api = Build.VERSION_CODES.M)
private Cipher createCipher(int mode) throws NoSuchPaddingException, NoSuchAlgorithmException,
UnrecoverableKeyException, KeyStoreException, InvalidKeyException,
InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" +
KeyProperties.ENCRYPTION_PADDING_PKCS7);
Key key = keyStore.getKey(MY_APP_ALIAS, null);
if (key == null) {
return null;
}
if(mode == Cipher.ENCRYPT_MODE) {
cipher.init(mode, key);
byte[] iv = cipher.getIV();
saveIv(iv);
} else {
byte[] lastIv = getLastIv();
cipher.init(mode, key, new IvParameterSpec(lastIv));
}
return cipher;
}
@NonNull
@RequiresApi(api = Build.VERSION_CODES.M)
private KeyGenParameterSpec createKeyGenParameterSpec() {
return new KeyGenParameterSpec.Builder(MY_APP_ALIAS, KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
GoalKicker.com – Android™ Notes for Professionals 416
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build();
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean initKeyStore() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
keyStore.load(null);
if (getLastIv() == null) {
KeyGenParameterSpec keyGeneratorSpec = createKeyGenParameterSpec();
keyGenerator.init(keyGeneratorSpec);
keyGenerator.generateKey();
}
} catch (Throwable t) {
setError("Failed init of keyStore & keyGenerator: " + t.getMessage());
return false;
}
return true;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void authenticate(CancellationSignal cancellationSignal,
FingerPrintAuthenticationListener authListener, int mode) {
try {
if (hasPermission()) {
Cipher cipher = createCipher(mode);
FingerprintManager.CryptoObject crypto = new
FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(crypto, cancellationSignal, 0, authListener, null);
} else {
authListener.getCallback().onFailure("User hasn't granted permission to use
Fingerprint");
}
} catch (Throwable t) {
authListener.getCallback().onFailure("An error occurred: " + t.getMessage());
}
}
private String getSavedEncryptedPassword() {
SharedPreferences sharedPreferences = getSharedPreferences();
if (sharedPreferences != null) {
return sharedPreferences.getString(ENCRYPTED_PASS_SHARED_PREF_KEY, null);
}
return null;
}
private void saveEncryptedPassword(String encryptedPassword) {
SharedPreferences.Editor edit = getSharedPreferences().edit();
edit.putString(ENCRYPTED_PASS_SHARED_PREF_KEY, encryptedPassword);
edit.commit();
}
private byte[] getLastIv() {
SharedPreferences sharedPreferences = getSharedPreferences();
if (sharedPreferences != null) {
String ivString = sharedPreferences.getString(LAST_USED_IV_SHARED_PREF_KEY, null);
if (ivString != null) {
return decodeBytes(ivString);
GoalKicker.com – Android™ Notes for Professionals 417
}
}
return null;
}
private void saveIv(byte[] iv) {
SharedPreferences.Editor edit = getSharedPreferences().edit();
String string = encodeBytes(iv);
edit.putString(LAST_USED_IV_SHARED_PREF_KEY, string);
edit.commit();
}
private SharedPreferences getSharedPreferences() {
return context.getSharedPreferences(FINGER_PRINT_HELPER, 0);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean hasPermission() {
return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) ==
PackageManager.PERMISSION_GRANTED;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void savePassword(@NonNull String password, CancellationSignal cancellationSignal,
Callback callback) {
authenticate(cancellationSignal, new FingerPrintEncryptPasswordListener(callback,
password), Cipher.ENCRYPT_MODE);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void getPassword(CancellationSignal cancellationSignal, Callback callback) {
authenticate(cancellationSignal, new FingerPrintDecryptPasswordListener(callback),
Cipher.DECRYPT_MODE);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public boolean encryptPassword(Cipher cipher, String password) {
try {
// Encrypt the text
if(password.isEmpty()) {
setError("Password is empty");
return false;
}
if (cipher == null) {
setError("Could not create cipher");
return false;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
byte[] bytes = password.getBytes(Charset.defaultCharset());
cipherOutputStream.write(bytes);
cipherOutputStream.flush();
cipherOutputStream.close();
saveEncryptedPassword(encodeBytes(outputStream.toByteArray()));
} catch (Throwable t) {
setError("Encryption failed " + t.getMessage());
return false;
}
return true;
GoalKicker.com – Android™ Notes for Professionals 418
}
private byte[] decodeBytes(String s) {
final int len = s.length();
// "111" is not a valid hex encoding.
if( len%2 != 0 )
throw new IllegalArgumentException("hexBinary needs to be even-length: "+s);
byte[] out = new byte[len/2];
for( int i=0; i<len; i+=2 ) {
int h = hexToBin(s.charAt(i ));
int l = hexToBin(s.charAt(i+1));
if( h==-1 || l==-1 )
throw new IllegalArgumentException("contains illegal character for hexBinary: "+s);
out[i/2] = (byte)(h*16+l);
}
return out;
}
private static int hexToBin( char ch ) {
if( '0'<=ch && ch<='9' ) return ch-'0';
if( 'A'<=ch && ch<='F' ) return ch-'A'+10;
if( 'a'<=ch && ch<='f' ) return ch-'a'+10;
return -1;
}
private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
public String encodeBytes(byte[] data) {
StringBuilder r = new StringBuilder(data.length*2);
for ( byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
@NonNull
private String decipher(Cipher cipher) throws IOException, IllegalBlockSizeException,
BadPaddingException {
String retVal = null;
String savedEncryptedPassword = getSavedEncryptedPassword();
if (savedEncryptedPassword != null) {
byte[] decodedPassword = decodeBytes(savedEncryptedPassword);
CipherInputStream cipherInputStream = new CipherInputStream(new
ByteArrayInputStream(decodedPassword), cipher);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte) nextByte);
}
cipherInputStream.close();
byte[] bytes = new byte[values.size()];
for (int i = 0; i < values.size(); i++) {
bytes[i] = values.get(i).byteValue();
}
GoalKicker.com – Android™ Notes for Professionals 419
retVal = new String(bytes, Charset.defaultCharset());
}
return retVal;
}
private void setError(String error) {
lastError = error;
Log.w(FINGER_PRINT_HELPER, lastError);
}
@RequiresApi(Build.VERSION_CODES.M)
protected class FingerPrintAuthenticationListener extends
FingerprintManager.AuthenticationCallback {
protected final Callback callback;
public FingerPrintAuthenticationListener(@NonNull Callback callback) {
this.callback = callback;
}
public void onAuthenticationError(int errorCode, CharSequence errString) {
callback.onFailure("Authentication error [" + errorCode + "] " + errString);
}
/**
* Called when a recoverable error has been encountered during authentication. The help
* string is provided to give the user guidance for what went wrong, such as
* "Sensor dirty, please clean it."
* @param helpCode An integer identifying the error message
* @param helpString A human-readable string that can be shown in UI
*/
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
callback.onHelp(helpCode, helpString.toString());
}
/**
* Called when a fingerprint is recognized.
* @param result An object containing authentication-related data
*/
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
}
/**
* Called when a fingerprint is valid but not recognized.
*/
public void onAuthenticationFailed() {
callback.onFailure("Authentication failed");
}
public @NonNull
Callback getCallback() {
return callback;
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private class FingerPrintEncryptPasswordListener extends FingerPrintAuthenticationListener {
private final String password;
public FingerPrintEncryptPasswordListener(Callback callback, String password) {
GoalKicker.com – Android™ Notes for Professionals 420
super(callback);
this.password = password;
}
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
try {
if (encryptPassword(cipher, password)) {
callback.onSuccess("Encrypted");
} else {
callback.onFailure("Encryption failed");
}
} catch (Exception e) {
callback.onFailure("Encryption failed " + e.getMessage());
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
protected class FingerPrintDecryptPasswordListener extends FingerPrintAuthenticationListener {
public FingerPrintDecryptPasswordListener(@NonNull Callback callback) {
super(callback);
}
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
try {
String savedPass = decipher(cipher);
if (savedPass != null) {
callback.onSuccess(savedPass);
} else {
callback.onFailure("Failed deciphering");
}
} catch (Exception e) {
callback.onFailure("Deciphering failed " + e.getMessage());
}
}
}
}
This activity below is a very basic example of how to get a user saved password and interact with the helper.
public class MainActivity extends AppCompatActivity {
private TextView passwordTextView;
private FingerPrintAuthHelper fingerPrintAuthHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
passwordTextView = (TextView) findViewById(R.id.password);
errorTextView = (TextView) findViewById(R.id.error);
View savePasswordButton = findViewById(R.id.set_password_button);
savePasswordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
GoalKicker.com – Android™ Notes for Professionals 421
fingerPrintAuthHelper.savePassword(passwordTextView.getText().toString(), new
CancellationSignal(), getAuthListener(false));
}
}
});
View getPasswordButton = findViewById(R.id.get_password_button);
getPasswordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerPrintAuthHelper.getPassword(new CancellationSignal(),
getAuthListener(true));
}
}
});
}
// Start the finger print helper. In case this fails show error to user
private void startFingerPrintAuthHelper() {
fingerPrintAuthHelper = new FingerPrintAuthHelper(this);
if (!fingerPrintAuthHelper.init()) {
errorTextView.setText(fingerPrintAuthHelper.getLastError());
}
}
@NonNull
private FingerPrintAuthHelper.Callback getAuthListener(final boolean isGetPass) {
return new FingerPrintAuthHelper.Callback() {
@Override
public void onSuccess(String result) {
if (isGetPass) {
errorTextView.setText("Success!!! Pass = " + result);
} else {
errorTextView.setText("Encrypted pass = " + result);
}
}
@Override
public void onFailure(String message) {
errorTextView.setText("Failed - " + message);
}
@Override
public void onHelp(int helpCode, String helpString) {
errorTextView.setText("Help needed - " + helpString);
}
};
}
}
Section 55.2: Adding the Fingerprint Scanner in Android
application
Android supports fingerprint api from Android 6.0 (Marshmallow) SDK 23
To use this feature in your app, first add the USE_FINGERPRINT permission in your manifest.
<uses-permission
GoalKicker.com – Android™ Notes for Professionals 422
android:name="android.permission.USE_FINGERPRINT" />
Here the procedure to follow
First you need to create a symmetric key in the Android Key Store using KeyGenerator which can be only
be used after the user has authenticated with fingerprint and pass a KeyGenParameterSpec.
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
.setUserAuthenticationRequired(true)
.build());
keyPairGenerator.generateKeyPair();
By setting KeyGenParameterSpec.Builder.setUserAuthenticationRequired to true, you can permit the use
of the key only after the user authenticate it including when authenticated with the user's fingerprint.
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PublicKey publicKey =
keyStore.getCertificate(MainActivity.KEY_NAME).getPublicKey();
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey key = (PrivateKey) keyStore.getKey(KEY_NAME, null);
Then start listening to a fingerprint on the fingerprint sensor by calling FingerprintManager.authenticate
with a Cipher initialized with the symmetric key created. Or alternatively you can fall back to server-side
verified password as an authenticator.
Create and initialise the FingerprintManger from fingerprintManger.class
getContext().getSystemService(FingerprintManager.class)
To authenticate use FingerprintManger api and create subclass using
FingerprintManager.AuthenticationCallback and override the methods
onAuthenticationError
onAuthenticationHelp
onAuthenticationSucceeded
onAuthenticationFailed
To Start
To startListening the fingerPrint event call authenticate method with crypto
fingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 , this, null);
GoalKicker.com – Android™ Notes for Professionals 423
Cancel
to stop listenting the scanner call
android.os.CancellationSignal;
Once the fingerprint (or password) is verified, the
FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded() callback is called.
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
}
GoalKicker.com – Android™ Notes for Professionals 424
Chapter 56: Bluetooth and Bluetooth LE
API
Section 56.1: Permissions
Add this permission to the manifest file to use Bluetooth features in your application:
<uses-permission android:name="android.permission.BLUETOOTH" />
If you need to initiate device discovery or manipulate Bluetooth settings, you also need to add this permission:
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
Targetting Android API level 23 and above, will require location access:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- OR -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
* Also see the Permissions topic for more details on how to use permissions appropriately.
Section 56.2: Check if bluetooth is enabled
private static final int REQUEST_ENABLE_BT = 1; // Unique request code
BluetoothAdapter mBluetoothAdapter;
// ...
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// ...
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
// Bluetooth was enabled
} else if (resultCode == RESULT_CANCELED) {
// Bluetooth was not enabled
}
}
}
Section 56.3: Find nearby Bluetooth Low Energy devices
The BluetoothLE API was introduced in API 18. However, the way of scanning devices has changed in API 21. The
searching of devices must start with defining the service UUID that is to be scanned (either officailly adopted 16-bit
UUID's or proprietary ones). This example illustrates, how to make an API independent way of searching for BLE
devices:
GoalKicker.com – Android™ Notes for Professionals 425
1. Create bluetooth device model:
public class BTDevice {
String address;
String name;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2. Define Bluetooth Scanning interface:
public interface ScanningAdapter {
void startScanning(String name, String[] uuids);
void stopScanning();
List<BTDevice> getFoundDeviceList();
}
3. Create scanning factory class:
public class BluetoothScanningFactory implements ScanningAdapter {
private ScanningAdapter mScanningAdapter;
public BluetoothScanningFactory() {
if (isNewerAPI()) {
mScanningAdapter = new LollipopBluetoothLEScanAdapter();
} else {
mScanningAdapter = new JellyBeanBluetoothLEScanAdapter();
}
}
private boolean isNewerAPI() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
@Override
public void startScanning(String[] uuids) {
mScanningAdapter.startScanning(uuids);
}
@Override
public void stopScanning() {
mScanningAdapter.stopScanning();
}
@Override
public List<BTDevice> getFoundDeviceList() {
GoalKicker.com – Android™ Notes for Professionals 426
return mScanningAdapter.getFoundDeviceList();
}
}
4. Create factory implementation for each API:
API 18:
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.Build;
import android.os.Parcelable;
import android.util.Log;
import bluetooth.model.BTDevice;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class JellyBeanBluetoothLEScanAdapter implements ScanningAdapter{
BluetoothAdapter bluetoothAdapter;
ScanCallback mCallback;
List<BTDevice> mBluetoothDeviceList;
public JellyBeanBluetoothLEScanAdapter() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mCallback = new ScanCallback();
mBluetoothDeviceList = new ArrayList<>();
}
@Override
public void startScanning(String[] uuids) {
if (uuids == null || uuids.length == 0) {
return;
}
UUID[] uuidList = createUUIDList(uuids);
bluetoothAdapter.startLeScan(uuidList, mCallback);
}
private UUID[] createUUIDList(String[] uuids) {
UUID[] uuidList = new UUID[uuids.length];
for (int i = 0 ; i < uuids.length ; ++i) {
String uuid = uuids[i];
if (uuid == null) {
continue;
}
uuidList[i] = UUID.fromString(uuid);
}
return uuidList;
}
@Override
public void stopScanning() {
bluetoothAdapter.stopLeScan(mCallback);
}
@Override
public List<BTDevice> getFoundDeviceList() {
GoalKicker.com – Android™ Notes for Professionals 427
return mBluetoothDeviceList;
}
private class ScanCallback implements BluetoothAdapter.LeScanCallback {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
if (isAlreadyAdded(device)) {
return;
}
BTDevice btDevice = new BTDevice();
btDevice.setName(new String(device.getName()));
btDevice.setAddress(device.getAddress());
mBluetoothDeviceList.add(btDevice);
Log.d("Bluetooth discovery", device.getName() + " " + device.getAddress());
Parcelable[] uuids = device.getUuids();
String uuid = "";
if (uuids != null) {
for (Parcelable ep : uuids) {
uuid += ep + " ";
}
Log.d("Bluetooth discovery", device.getName() + " " + device.getAddress() + " " +
uuid);
}
}
private boolean isAlreadyAdded(BluetoothDevice bluetoothDevice) {
for (BTDevice device : mBluetoothDeviceList) {
String alreadyAddedDeviceMACAddress = device.getAddress();
String newDeviceMACAddress = bluetoothDevice.getAddress();
if (alreadyAddedDeviceMACAddress.equals(newDeviceMACAddress)) {
return true;
}
}
return false;
}
}
}
API 21:
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.os.Build;
import android.os.ParcelUuid;
import bluetooth.model.BTDevice;
import java.util.ArrayList;
import java.util.List;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class LollipopBluetoothLEScanAdapter implements ScanningAdapter {
BluetoothLeScanner bluetoothLeScanner;
ScanCallback mCallback;
List<BTDevice> mBluetoothDeviceList;
GoalKicker.com – Android™ Notes for Professionals 428
public LollipopBluetoothLEScanAdapter() {
bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
mCallback = new ScanCallback();
mBluetoothDeviceList = new ArrayList<>();
}
@Override
public void startScanning(String[] uuids) {
if (uuids == null || uuids.length == 0) {
return;
}
List<ScanFilter> filterList = createScanFilterList(uuids);
ScanSettings scanSettings = createScanSettings();
bluetoothLeScanner.startScan(filterList, scanSettings, mCallback);
}
private List<ScanFilter> createScanFilterList(String[] uuids) {
List<ScanFilter> filterList = new ArrayList<>();
for (String uuid : uuids) {
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString(uuid))
.build();
filterList.add(filter);
};
return filterList;
}
private ScanSettings createScanSettings() {
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.build();
return settings;
}
@Override
public void stopScanning() {
bluetoothLeScanner.stopScan(mCallback);
}
@Override
public List<BTDevice> getFoundDeviceList() {
return mBluetoothDeviceList;
}
public class ScanCallback extends android.bluetooth.le.ScanCallback {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (result == null) {
return;
}
BTDevice device = new BTDevice();
device.setAddress(result.getDevice().getAddress());
device.setName(new StringBuffer(result.getScanRecord().getDeviceName()).toString());
if (device == null || device.getAddress() == null) {
return;
}
if (isAlreadyAdded(device)) {
return;
}
mBluetoothDeviceList.add(device);
GoalKicker.com – Android™ Notes for Professionals 429
}
private boolean isAlreadyAdded(BTDevice bluetoothDevice) {
for (BTDevice device : mBluetoothDeviceList) {
String alreadyAddedDeviceMACAddress = device.getAddress();
String newDeviceMACAddress = bluetoothDevice.getAddress();
if (alreadyAddedDeviceMACAddress.equals(newDeviceMACAddress)) {
return true;
}
}
return false;
}
}
}
5. Get found device list by calling:
scanningFactory.startScanning({uuidlist});
wait few seconds...
List<BTDevice> bluetoothDeviceList = scanningFactory.getFoundDeviceList();
Section 56.4: Make device discoverable
private static final int REQUEST_DISCOVERABLE_BT = 2; // Unique request code
private static final int DISCOVERABLE_DURATION = 120; // Discoverable duration time in seconds
// 0 means always discoverable
// maximum value is 3600
// ...
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, DISCOVERABLE_DURATION);
startActivityForResult(discoverableIntent, REQUEST_DISCOVERABLE_BT);
// ...
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_DISCOVERABLE_BT) {
if (resultCode == RESULT_OK) {
// Device is discoverable
} else if (resultCode == RESULT_CANCELED) {
// Device is not discoverable
}
}
}
Section 56.5: Connect to Bluetooth device
After you obtained BluetoothDevice, you can communicate with it. This kind of communication performed by using
socket input\output streams:
Those are the basic steps for Bluetooth communication establishment:
GoalKicker.com – Android™ Notes for Professionals 430
1) Initialize socket:
private BluetoothSocket _socket;
//...
public InitializeSocket(BluetoothDevice device){
try {
_socket = device.createRfcommSocketToServiceRecord(<Your app UDID>);
} catch (IOException e) {
//Error
}
}
2) Connect to socket:
try {
_socket.connect();
} catch (IOException connEx) {
try {
_socket.close();
} catch (IOException closeException) {
//Error
}
}
if (_socket != null && _socket.isConnected()) {
//Socket is connected, now we can obtain our IO streams
}
3) Obtaining socket Input\Output streams
private InputStream _inStream;
private OutputStream _outStream;
//....
try {
_inStream = _socket.getInputStream();
_outStream = _socket.getOutputStream();
} catch (IOException e) {
//Error
}
Input stream - Used as incoming data channel (receive data from connected device)
Output stream - Used as outgoing data channel (send data to connected device)
After finishing 3rd step, we can receive and send data between both devices using previously initialized
streams:
1) Receiving data (reading from socket input stream)
byte[] buffer = new byte[1024]; // buffer (our data)
int bytesCount; // amount of read bytes
while (true) {
try {
//reading data from input stream
bytesCount = _inStream.read(buffer);
if(buffer != null && bytesCount > 0)
{
//Parse received bytes
GoalKicker.com – Android™ Notes for Professionals 431
}
} catch (IOException e) {
//Error
}
}
2) Sending data (Writing to output stream)
public void write(byte[] bytes) {
try {
_outStream.write(bytes);
} catch (IOException e) {
//Error
}
}
Of course, connection, reading and writing functionality should be done in a dedicated thread.
Sockets and Stream objects need to be
Section 56.6: Find nearby bluetooth devices
Declare a BluetoothAdapter first.
BluetoothAdapter mBluetoothAdapter;
Now create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//Device found
if (BluetoothDevice.ACTION_FOUND.equals(action))
{
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a list
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
Then start discovering the nearby bluetooth devices by calling startDiscovery
mBluetoothAdapter.startDiscovery();
Don't forget to unregister the BroadcastReceiver inside onDestroy
unregisterReceiver(mReceiver);
GoalKicker.com – Android™ Notes for Professionals 432
Chapter 57: Runtime Permissions in API-23
+
Android Marshmallow introduced Runtime Permission model. Permissions are categorized into two categories i.e.
Normal and Dangerous Permissions. where dangerous permissions are now granted by the user at run time.
Section 57.1: Android 6.0 multiple permissions
This example shows how to check permissions at runtime in Android 6 and later.
public static final int MULTIPLE_PERMISSIONS = 10; // code you want.
String[] permissions = new String[] {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
};
@Override
void onStart() {
if (checkPermissions()){
// permissions granted.
} else {
// show dialog informing them that we lack certain permissions
}
}
private boolean checkPermissions() {
int result;
List<String> listPermissionsNeeded = new ArrayList<>();
for (String p:permissions) {
result = ContextCompat.checkSelfPermission(getActivity(),p);
if (result != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(p);
}
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new
String[listPermissionsNeeded.size()]), MULTIPLE_PERMISSIONS);
return false;
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MULTIPLE_PERMISSIONS:{
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
// permissions granted.
} else {
// no permissions granted.
}
return;
}
}
}
GoalKicker.com – Android™ Notes for Professionals 433
Section 57.2: Multiple Runtime Permissions From Same
Permission Groups
In the manifest we have fours dangerous runtime permissions from two groups.
<!-- Required to read and write to shredPref file. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- Required to get location of device. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
In the activity where the permissions are required. Note it is important to check for permissions in any activity that
requires permissions, as the permissions can be revoked while the app is in the background and the app will then
crash.
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_layout);
// A simple check of whether runtime permissions need to be managed
if (Build.VERSION.SDK_INT >= 23) {
checkMultiplePermissions();
}
We only need to ask for permission for one of these from each group and all other permissions from this group are
granted unless the permission is revoked by the user.
private void checkMultiplePermissions() {
if (Build.VERSION.SDK_INT >= 23) {
List<String> permissionsNeeded = new ArrayList<String>();
List<String> permissionsList = new ArrayList<String>();
if (!addPermission(permissionsList, android.Manifest.permission.ACCESS_FINE_LOCATION)) {
permissionsNeeded.add("GPS");
}
if (!addPermission(permissionsList, android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
permissionsNeeded.add("Read Storage");
}
if (permissionsList.size() > 0) {
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
return;
}
}
}
private boolean addPermission(List<String> permissionsList, String permission) {
if (Build.VERSION.SDK_INT >= 23)
GoalKicker.com – Android™ Notes for Professionals 434
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
// Check for Rationale Option
if (!shouldShowRequestPermissionRationale(permission))
return false;
}
return true;
}
This deals with the result of the user allowing or not allowing permissions. In this example, if the permissions are
not allowed, the app is killed.
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<String, Integer>();
// Initial
perms.put(android.Manifest.permission.ACCESS_FINE_LOCATION,
PackageManager.PERMISSION_GRANTED);
perms.put(android.Manifest.permission.READ_EXTERNAL_STORAGE,
PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
if (perms.get(android.Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
&& perms.get(android.Manifest.permission.READ_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED) {
// All Permissions Granted
return;
} else {
// Permission Denied
if (Build.VERSION.SDK_INT >= 23) {
Toast.makeText(
getApplicationContext(),
"My App cannot run without Location and Storage " +
"Permissions.\nRelaunch My App or allow permissions" +
" in Applications Settings",
Toast.LENGTH_LONG).show();
finish();
}
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
More Information
https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en
Section 57.3: Using PermissionUtil
PermissionUtil is a simple and convenient way of asking for permissions in context. You can easily provide what
should happen in case of all requested permissions granted (onAllGranted()), any request was denied
GoalKicker.com – Android™ Notes for Professionals 435
(onAnyDenied()) or in case that a rational is needed (onRational()).
Anywhere in your AppCompatActivity or Fragment that you want to ask for user's permisssion
mRequestObject =
PermissionUtil.with(this).request(Manifest.permission.WRITE_EXTERNAL_STORAGE).onAllGranted(
new Func() {
@Override protected void call() {
//Happy Path
}
}).onAnyDenied(
new Func() {
@Override protected void call() {
//Sad Path
}
}).ask(REQUEST_CODE_STORAGE);
And add this to onRequestPermissionsResult
if(mRequestObject!=null){
mRequestObject.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
Add the requested permission to your AndroidManifest.xml as well
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Section 57.4: Include all permission-related code to an
abstract base class and extend the activity of this base class
to achieve cleaner/reusable code
public abstract class BaseActivity extends AppCompatActivity {
private Map<Integer, PermissionCallback> permissionCallbackMap = new HashMap<>();
@Override
protected void onStart() {
super.onStart();
...
}
@Override
public void setContentView(int layoutResId) {
super.setContentView(layoutResId);
bindViews();
}
...
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionCallback callback = permissionCallbackMap.get(requestCode);
if (callback == null) return;
// Check whether the permission request was rejected.
if (grantResults.length < 0 && permissions.length > 0) {
callback.onPermissionDenied(permissions);
GoalKicker.com – Android™ Notes for Professionals 436
return;
}
List<String> grantedPermissions = new ArrayList<>();
List<String> blockedPermissions = new ArrayList<>();
List<String> deniedPermissions = new ArrayList<>();
int index = 0;
for (String permission : permissions) {
List<String> permissionList = grantResults[index] == PackageManager.PERMISSION_GRANTED
? grantedPermissions
: ! ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
? blockedPermissions
: deniedPermissions;
permissionList.add(permission);
index ++;
}
if (grantedPermissions.size() > 0) {
callback.onPermissionGranted(
grantedPermissions.toArray(new String[grantedPermissions.size()]));
}
if (deniedPermissions.size() > 0) {
callback.onPermissionDenied(
deniedPermissions.toArray(new String[deniedPermissions.size()]));
}
if (blockedPermissions.size() > 0) {
callback.onPermissionBlocked(
blockedPermissions.toArray(new String[blockedPermissions.size()]));
}
permissionCallbackMap.remove(requestCode);
}
/**
* Check whether a permission is granted or not.
*
* @param permission
* @return
*/
public boolean hasPermission(String permission) {
return ContextCompat.checkSelfPermission(this, permission) ==
PackageManager.PERMISSION_GRANTED;
}
/**
* Request permissions and get the result on callback.
*
* @param permissions
* @param callback
*/
public void requestPermission(String [] permissions, @NonNull PermissionCallback callback) {
int requestCode = permissionCallbackMap.size() + 1;
permissionCallbackMap.put(requestCode, callback);
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
/**
* Request permission and get the result on callback.
*
GoalKicker.com – Android™ Notes for Professionals 437
* @param permission
* @param callback
*/
public void requestPermission(String permission, @NonNull PermissionCallback callback) {
int requestCode = permissionCallbackMap.size() + 1;
permissionCallbackMap.put(requestCode, callback);
ActivityCompat.requestPermissions(this, new String[] { permission }, requestCode);
}
}
Example usage in the activity
The activity should extend the abstract base class defined above as follows:
private void requestLocationAfterPermissionCheck() {
if (hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
requestLocation();
return;
}
// Call the base class method.
requestPermission(Manifest.permission.ACCESS_FINE_LOCATION, new PermissionCallback() {
@Override
public void onPermissionGranted(String[] grantedPermissions) {
requestLocation();
}
@Override
public void onPermissionDenied(String[] deniedPermissions) {
// Do something.
}
@Override
public void onPermissionBlocked(String[] blockedPermissions) {
// Do something.
}
});
}
Section 57.5: Enforcing Permissions in Broadcasts, URI
You can do a permissions check when sending an Intent to a registered broadcast receiver. The permissions you
send are cross-checked with the ones registered under the tag. They restrict who can send broadcasts to the
associated receiver.
To send a broadcast request with permissions, specify the permission as a string in the
Context.sendBroadcast(Intent intent, String permission) call, but keep in mind that the Receiver's app MUST
have that permission in order to receive your broadcast. The receiver should be installed first before the sender.
The method signature is:
void sendBroadcast (Intent intent, String receiverPermission)
//for example to send a broadcast to Bcastreceiver receiver
Intent broadcast = new Intent(this, Bcastreceiver.class);
sendBroadcast(broadcast, "org.quadcore.mypermission");
and you can specify in your manifest that the broadcast sender is required to include the requested permission
sent through the sendBroadcast:
GoalKicker.com – Android™ Notes for Professionals 438
<!-- Your special permission -->
<permission android:name="org.quadcore.mypermission"
android:label="my_permission"
android:protectionLevel="dangerous"></permission>
Also declare the permission in the manifest of the application that is supposed to receive this broadcast:
<!-- I use the permission ! -->
<uses-permission android:name="org.quadcore.mypermission"/>
<!-- along with the receiver -->
<receiver android:name="Bcastreceiver" android:exported="true" />
Note: Both a receiver and a broadcaster can require a permission, and when this happens, both permission checks
must pass for the Intent to be delivered to the associated target. The App that defines the permission should be
installed first.
Find the full documentation here on Permissions.
GoalKicker.com – Android™ Notes for Professionals 439
Chapter 58: Android Places API
Section 58.1: Getting Current Places by Using Places API
You can get the current location and local places of user by using the Google Places API.
Ar first, you should call the PlaceDetectionApi.getCurrentPlace() method in order to retrieve local business or
other places. This method returns a PlaceLikelihoodBuffer object which contains a list of PlaceLikelihood
objects. Then, you can get a Place object by calling the PlaceLikelihood.getPlace() method.
Important: You must request and obtain the ACCESS_FINE_LOCATION permission in order to allow your app to
access precise location information.
private static final int PERMISSION_REQUEST_TO_ACCESS_LOCATION = 1;
private TextView txtLocation;
private GoogleApiClient googleApiClient;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location);
txtLocation = (TextView) this.findViewById(R.id.txtLocation);
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Places.GEO_DATA_API)
.addApi(Places.PLACE_DETECTION_API)
.enableAutoManage(this, this)
.build();
getCurrentLocation();
}
private void getCurrentLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
Log.e(LOG_TAG, "Permission is not granted");
ActivityCompat.requestPermissions(this,new
String[]{Manifest.permission.ACCESS_FINE_LOCATION},PERMISSION_REQUEST_TO_ACCESS_LOCATION);
return;
}
Log.i(LOG_TAG, "Permission is granted");
PendingResult<PlaceLikelihoodBuffer> result =
Places.PlaceDetectionApi.getCurrentPlace(googleApiClient, null);
result.setResultCallback(new ResultCallback<PlaceLikelihoodBuffer>() {
@Override
public void onResult(PlaceLikelihoodBuffer likelyPlaces) {
Log.i(LOG_TAG, String.format("Result received : %d " , likelyPlaces.getCount() ));
StringBuilder stringBuilder = new StringBuilder();
for (PlaceLikelihood placeLikelihood : likelyPlaces) {
stringBuilder.append(String.format("Place : '%s' %n",
placeLikelihood.getPlace().getName()));
}
likelyPlaces.release();
txtLocation.setText(stringBuilder.toString());
GoalKicker.com – Android™ Notes for Professionals 440
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_TO_ACCESS_LOCATION: {
// If the request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getCurrentLocation();
} else {
// Permission denied, boo!
// Disable the functionality that depends on this permission.
}
return;
}
// Add further 'case' lines to check for other permissions this app might request.
}
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Log.e(LOG_TAG, "GoogleApiClient connection failed: " + connectionResult.getErrorMessage());
}
Section 58.2: Place Autocomplete Integration
The autocomplete feature in the Google Places API for Android provides place predictions to user. While user types
in the search box, autocomplete shows places according to user's queries.
AutoCompleteActivity.java
private TextView txtSelectedPlaceName;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_autocomplete);
txtSelectedPlaceName = (TextView) this.findViewById(R.id.txtSelectedPlaceName);
PlaceAutocompleteFragment autocompleteFragment = (PlaceAutocompleteFragment)
getFragmentManager().findFragmentById(R.id.fragment_autocomplete);
autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
@Override
public void onPlaceSelected(Place place) {
Log.i(LOG_TAG, "Place: " + place.getName());
txtSelectedPlaceName.setText(String.format("Selected places : %s - %s" ,
place.getName(), place.getAddress()));
}
@Override
public void onError(Status status) {
Log.i(LOG_TAG, "An error occurred: " + status);
Toast.makeText(AutoCompleteActivity.this, "Place cannot be selected!!",
Toast.LENGTH_SHORT).show();
}
GoalKicker.com – Android™ Notes for Professionals 441
});
}
}
activity_autocomplete.xml
<fragment
android:id="@+id/fragment_autocomplete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:name="com.google.android.gms.location.places.ui.PlaceAutocompleteFragment"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtSelectedPlaceName"
android:layout_margin="20dp"
android:padding="15dp"
android:hint="@string/txt_select_place_hint"
android:textSize="@dimen/place_autocomplete_prediction_primary_text"/>
Section 58.3: Place Picker Usage Example
Place Picker is a really simple UI widget provided by Places API. It provides a built-in map, current location, nearby
places, search abilities and autocomplete.
This is a sample usage of Place Picker UI widget.
private static int PLACE_PICKER_REQUEST = 1;
private TextView txtPlaceName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_place_picker_sample);
txtPlaceName = (TextView) this.findViewById(R.id.txtPlaceName);
Button btnSelectPlace = (Button) this.findViewById(R.id.btnSelectPlace);
btnSelectPlace.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openPlacePickerView();
}
});
}
private void openPlacePickerView(){
PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
try {
startActivityForResult(builder.build(this), PLACE_PICKER_REQUEST);
} catch (GooglePlayServicesRepairableException e) {
e.printStackTrace();
GoalKicker.com – Android™ Notes for Professionals 442
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PLACE_PICKER_REQUEST) {
if (resultCode == RESULT_OK) {
Place place = PlacePicker.getPlace(this, data);
Log.i(LOG_TAG, String.format("Place Name : %s", place.getName()));
Log.i(LOG_TAG, String.format("Place Address : %s", place.getAddress()));
Log.i(LOG_TAG, String.format("Place Id : %s", place.getId()));
txtPlaceName.setText(String.format("Place : %s - %s" , place.getName() ,
place.getAddress()));
}
}
}
Section 58.4: Setting place type filters for PlaceAutocomplete
In some scenarios, we might want to narrow down the results being shown by PlaceAutocomplete to a specific
country or maybe to show only Regions. This can be achieved by setting an AutocompleteFilter on the intent. For
example, if I want to look only for places of type REGION and only belonging to India, I would do the following:
MainActivity.java
public class MainActivity extends AppComatActivity {
private static final int PLACE_AUTOCOMPLETE_REQUEST_CODE = 1;
private TextView selectedPlace;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
selectedPlace = (TextView) findViewById(R.id.selected_place);
try {
AutocompleteFilter typeFilter = new AutocompleteFilter.Builder()
.setTypeFilter(AutocompleteFilter.TYPE_FILTER_REGIONS)
.setCountry("IN")
.build();
Intent intent =
new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
.setFilter(typeFilter)
.build(this);
startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE);
} catch (GooglePlayServicesRepairableException
| GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
}
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
GoalKicker.com – Android™ Notes for Professionals 443
final Place place = PlacePicker.getPlace(this, data);
selectedPlace.setText(place.getName().toString().toUpperCase());
} else {
Toast.makeText(MainActivity.this, "Could not get location.", Toast.LENGTH_SHORT).show();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/selected_place"/>
</LinearLayout>
The PlaceAutocomplete will launch automatically and you can then select a place from the results which will only
be of the type REGION and will only belong to the specified country. The intent can also be launched at the click of
a button.
Section 58.5: Adding more than one google auto complete
activity
public static final int PLACE_AUTOCOMPLETE_FROM_PLACE_REQUEST_CODE=1;
public static final int PLACE_AUTOCOMPLETE_TO_PLACE_REQUEST_CODE=2;
fromPlaceEdit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//Do your stuff from place
startActivityForResult(intent, PLACE_AUTOCOMPLETE_FROM_PLACE_REQUEST_CODE);
} catch (GooglePlayServicesRepairableException e) {
// TODO: Handle the error.
} catch (GooglePlayServicesNotAvailableException e) {
// TODO: Handle the error.
}
}
});
toPlaceEdit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//Do your stuff to place
startActivityForResult(intent, PLACE_AUTOCOMPLETE_TO_PLACE_REQUEST_CODE);
} catch (GooglePlayServicesRepairableException e) {
// TODO: Handle the error.
} catch (GooglePlayServicesNotAvailableException e) {
// TODO: Handle the error.
GoalKicker.com – Android™ Notes for Professionals 444
}
}
});
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PLACE_AUTOCOMPLETE_FROM_PLACE_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
//Do your ok >from place< stuff here
} else if (resultCode == PlaceAutocomplete.RESULT_ERROR) {
//Handle your error >from place<
} else if (resultCode == RESULT_CANCELED) {
// The user canceled the operation.
}
} else if (requestCode == PLACE_AUTOCOMPLETE_TO_PLACE_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
//Do your ok >to place< stuff here
} else if (resultCode == PlaceAutocomplete.RESULT_ERROR) {
//Handle your error >to place<
} else if (resultCode == RESULT_CANCELED) {
// The user canceled the operation.
}
}
}
GoalKicker.com – Android™ Notes for Professionals 445
Chapter 59: Android NDK
Section 59.1: How to log in ndk
First make sure you link against the logging library in your Android.mk file:
LOCAL_LDLIBS := -llog
Then use one of the following __android_log_print() calls:
#include <android/log.h>
#define TAG "MY LOG"
__android_log_print(ANDROID_LOG_VERBOSE, TAG, "The value of 1 + 1 is %d", 1 + 1)
__android_log_print(ANDROID_LOG_WARN, TAG, "The value of 1 + 1 is %d", 1 + 1)
__android_log_print(ANDROID_LOG_DEBUG, TAG, "The value of 1 + 1 is %d", 1 + 1)
__android_log_print(ANDROID_LOG_INFO, TAG, "The value of 1 + 1 is %d", 1 + 1)
__android_log_print(ANDROID_LOG_ERROR, TAG, "The value of 1 + 1 is %d", 1 + 1)
Or use those in a more convenient way by defining corresponding macros:
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
Example:
int x = 42;
LOGD("The value of x is %d", x);
Section 59.2: Building native executables for Android
project/jni/main.c
#include <stdio.h>
#include <unistd.h>
int main(void) {
printf("Hello world!\n");
return 0;
}
project/jni/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_world
LOCAL_SRC_FILES := main.c
include $(BUILD_EXECUTABLE)
project/jni/Application.mk
GoalKicker.com – Android™ Notes for Professionals 446
APP_ABI := all
APP_PLATFORM := android-21
If you want to support devices running Android versions lower than 5.0 (API 21), you need to compile your binary
with APP_PLATFORM set to an older API, e.g. android-8. This is a consequence of Android 5.0 enforcing Position
Independent Binaries (PIE), whereas older devices do not necessarily support PIEs. Therefore, you need to use either
the PIE or the non-PIE, depending on the device version. If you want to use the binary from within your Android
application, you need to check the API level and extract the correct binary.
APP_ABI can be changed to specific platforms such as armeabi to build the binary for those architectures only.
In the worst case, you will have both a PIE and a non-PIE binary for each architecture (about 14 different binaries
using ndk-r10e).
To build the executable:
cd project
ndk-build
You will find the binaries at project/libs/<architecture>/hello_world. You can use them via ADB (push and
chmod it with executable permission) or from your application (extract and chmod it with executable permission).
To determine the architecture of the CPU, retrieve the build property ro.product.cpu.abi for the primary
architecture or ro.product.cpu.abilist (on newer devices) for a complete list of supported architectures. You can
do this using the android.os.Build class from within your application or using getprop <name> via ADB.
Section 59.3: How to clean the build
If you need to clean the build:
ndk-build clean
Section 59.4: How to use a makefile other than Android.mk
ndk-build NDK_PROJECT_PATH=PROJECT_PATH APP_BUILD_SCRIPT=MyAndroid.mk
GoalKicker.com – Android™ Notes for Professionals 447
Chapter 60: DayNight Theme (AppCompat
v23.2 / API 14+)
Section 60.1: Adding the DayNight theme to an app
The DayNight theme gives an app the cool capability of switching color schemes based on the time of day and the
device's last known location.
Add the following to your styles.xml:
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
The themes you can extend from to add day night theme switching capability are the following:
"Theme.AppCompat.DayNight"
"Theme.AppCompat.DayNight.NoActionBar"
"Theme.AppCompat.DayNight.DarkActionBar"
Apart from colorPrimary, colorPrimaryDark and colorAccent, you can also add any other colors that you would
like to be switched, e.g. textColorPrimary or textColorSecondary. You can add your app's custom colors to this
style as well.
For theme switching to work, you need to define a default colors.xml in the res/values directory and another
colors.xml in the res/values-night directory and define day/night colors appropriately.
To switch the theme, call the AppCompatDelegate.setDefaultNightMode(int) method from your Java code. (This
will change the color scheme for the whole app, not just any one activity or fragment.) For example:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
You can pass any of the following three according to your choice:
AppCompatDelegate.MODE_NIGHT_NO: this sets the default theme for your app and takes the colors defined in
the res/values directory. It is recommended to use light colors for this theme.
AppCompatDelegate.MODE_NIGHT_YES: this sets a night theme for your app and takes the colors defined in the
res/values-night directory. It is recommended to use dark colors for this theme.
AppCompatDelegate.MODE_NIGHT_AUTO: this auto switches the colors of the app based on the time of the day
and the colors you have defined in values and values-night directories.
It is also possible to get the current night mode status using the getDefaultNightMode() method. For example:
int modeType = AppCompatDelegate.getDefaultNightMode();
Please note, however, that the theme switch will not persist if you kill the app and reopen it. If you do that, the
theme will switch back to AppCompatDelegate.MODE_NIGHT_AUTO, which is the default value. If you want the theme
switch to persist, make sure you store the value in shared preferences and load the stored value each time the app
is opened after it has been destroyed.
GoalKicker.com – Android™ Notes for Professionals 448
Chapter 61: Glide
**** WARNING This documentation is unmaintained and frequently inaccurate ****
Glide's official documentation is a much better source:
For Glide v4, see http://bumptech.github.io/glide/. For Glide v3, see https://github.com/bumptech/glide/wiki.
Section 61.1: Loading an image
ImageView
To load an image from a specified URL, Uri, resource id, or any other model into an ImageView:
ImageView imageView = (ImageView) findViewById(R.id.imageView);
String yourUrl = "http://www.yoururl.com/image.png";
Glide.with(context)
.load(yourUrl)
.into(imageView);
For Uris, replace yourUrl with your Uri (content://media/external/images/1). For Drawables replace yourUrl with
your resource id (R.drawable.image).
RecyclerView and ListView
In ListView or RecyclerView, you can use exactly the same lines:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
String currentUrl = myUrls.get(position);
Glide.with(context)
.load(currentUrl)
.into(myViewHolder.imageView);
}
If you don't want to start a load in onBindViewHolder, make sure you clear() any ImageView Glide may be
managing before modifying the ImageView:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
String currentUrl = myUrls.get(position);
if (TextUtils.isEmpty(currentUrl)) {
Glide.clear(viewHolder.imageView);
// Now that the view has been cleared, you can safely set your own resource
viewHolder.imageView.setImageResource(R.drawable.missing_image);
} else {
Glide.with(context)
.load(currentUrl)
.into(myViewHolder.imageView);
}
}
GoalKicker.com – Android™ Notes for Professionals 449
Section 61.2: Add Glide to your project
From the official documentation:
With Gradle:
repositories {
mavenCentral() // jcenter() works as well because it pulls from Maven Central
}
dependencies {
compile 'com.github.bumptech.glide:glide:4.0.0'
compile 'com.android.support:support-v4:25.3.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0'
}
With Maven:
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>glide</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>support-v4</artifactId>
<version>r7</version>
</dependency>
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>compiler</artifactId>
<version>4.0.0</version>
<optional>true</optional>
</dependency>
Depending on your ProGuard (DexGuard) config and usage, you may also need to include the following lines in your
proguard.cfg (See Glide's wiki for more info):
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
Section 61.3: Glide circle transformation (Load image in a
circular ImageView)
Create a circle image with glide.
public class CircleTransform extends BitmapTransformation {
public CircleTransform(Context context) {
super(context);
}
GoalKicker.com – Android™ Notes for Professionals 450
@Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int
outHeight) {
return circleCrop(pool, toTransform);
}
private static Bitmap circleCrop(BitmapPool pool, Bitmap source) {
if (source == null) return null;
int size = Math.min(source.getWidth(), source.getHeight());
int x = (source.getWidth() - size) / 2;
int y = (source.getHeight() - size) / 2;
Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);
Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
float r = size / 2f;
canvas.drawCircle(r, r, r, paint);
return result;
}
@Override public String getId() {
return getClass().getName();
}
}
Usage:
Glide.with(context)
.load(yourimageurl)
.transform(new CircleTransform(context))
.into(userImageView);
Section 61.4: Default transformations
Glide includes two default transformations, fit center and center crop.
Fit center:
Glide.with(context)
.load(yourUrl)
.fitCenter()
.into(yourView);
Fit center performs the same transformation as Android's ScaleType.FIT_CENTER.
Center crop:
Glide.with(context)
.load(yourUrl)
.centerCrop()
GoalKicker.com – Android™ Notes for Professionals 451
.into(yourView);
Center crop performs the same transformation as Android's ScaleType.CENTER_CROP.
For more information, see Glide's wiki.
Section 61.5: Glide rounded corners image with custom Glide
target
First make utility class or use this method in class needed
public class UIUtils {
public static BitmapImageViewTarget getRoundedImageTarget(@NonNull final Context context, @NonNull
final ImageView imageView,
final float radius) {
return new BitmapImageViewTarget(imageView) {
@Override
protected void setResource(final Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(context.getResources(), resource);
circularBitmapDrawable.setCornerRadius(radius);
imageView.setImageDrawable(circularBitmapDrawable);
}
};
}
Loading image:
Glide.with(context)
.load(imageUrl)
.asBitmap()
.into(UIUtils.getRoundedImageTarget(context, imageView, radius));
Because you use asBitmap() the animations will be removed though. You can use your own animation in this place
using the animate() method.
Example with similar fade in to default Glide animation.
Glide.with(context)
.load(imageUrl)
.asBitmap()
.animate(R.anim.abc_fade_in)
.into(UIUtils.getRoundedImageTarget(context, imageView, radius));
Please note this animation is support library private resource - it is unrecommended to use as it can change or even
be removed.
Note you also need to have support library to use RoundedBitmapDrawableFactory
Section 61.6: Placeholder and Error handling
If you want to add a Drawable be shown during the load, you can add a placeholder:
Glide.with(context)
.load(yourUrl)
.placeholder(R.drawable.placeholder)
GoalKicker.com – Android™ Notes for Professionals 452
.into(imageView);
If you want a Drawable to be shown if the load fails for any reason:
Glide.with(context)
.load(yourUrl)
.error(R.drawable.error)
.into(imageView);
If you want a Drawable to be shown if you provide a null model (URL, Uri, file path etc):
Glide.with(context)
.load(maybeNullUrl)
.fallback(R.drawable.fallback)
.into(imageView);
Section 61.7: Preloading images
To preload remote images and ensure that the image is only downloaded once:
Glide.with(context)
.load(yourUrl)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.preload();
Then:
Glide.with(context)
.load(yourUrl)
.diskCacheStrategy(DiskCacheStrategy.SOURCE) // ALL works here too
.into(imageView);
To preload local images and make sure a transformed copy is in the disk cache (and maybe the memory cache):
Glide.with(context)
.load(yourFilePathOrUri)
.fitCenter() // Or whatever transformation you want
.preload(200, 200); // Or whatever width and height you want
Then:
Glide.with(context)
.load(yourFilePathOrUri)
.fitCenter() // You must use the same transformation as above
.override(200, 200) // You must use the same width and height as above
.into(imageView);
Section 61.8: Handling Glide image load failed
Glide
.with(context)
.load(currentUrl)
.into(new BitmapImageViewTarget(profilePicture) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(context.getResources(), resource);
GoalKicker.com – Android™ Notes for Professionals 453
circularBitmapDrawable.setCornerRadius(radius);
imageView.setImageDrawable(circularBitmapDrawable);
}
@Override
public void onLoadFailed(@NonNull Exception e, Drawable errorDrawable) {
super.onLoadFailed(e, SET_YOUR_DEFAULT_IMAGE);
Log.e(TAG, e.getMessage(), e);
}
});
Here at SET_YOUR_DEFAULT_IMAGE place you can set any default Drawable. This image will be shown if Image loading
is failed.
Section 61.9: Load image in a circular ImageView without
custom transformations
Create a custom BitmapImageViewTarget to load the image into:
public class CircularBitmapImageViewTarget extends BitmapImageViewTarget
{
private Context context;
private ImageView imageView;
public CircularBitmapImageViewTarget(Context context, ImageView imageView)
{
super(imageView);
this.context = context;
this.imageView = imageView;
}
@Override
protected void setResource(Bitmap resource)
{
RoundedBitmapDrawable bitmapDrawable =
RoundedBitmapDrawableFactory.create(context.getResources(), resource);
bitmapDrawable.setCircular(true);
imageView.setImageDrawable(bitmapDrawable);
}
}
Usage:
Glide
.with(context)
.load(yourimageidentifier)
.asBitmap()
.into(new CircularBitmapImageViewTarget(context, imageView));
GoalKicker.com – Android™ Notes for Professionals 454
Chapter 62: Dialog
Line Description
show(); Shows the dialog
setContentView(R.layout.yourlayout); sets the ContentView of the dialog to your custom layout.
dismiss() Closes the dialog
Section 62.1: Adding Material Design AlertDialog to your app
using Appcompat
AlertDialog is a subclass of Dialog that can display one, two or three buttons. If you only want to display a String
in this dialog box, use the setMessage() method.
The AlertDialog from android.app package displays differently on different Android OS Versions.
The Android V7 Appcompat library provides an AlertDialog implementation which will display with Material Design
on all supported Android OS versions, as shown below:
First you need to add the V7 Appcompat library to your project. you can do this in the app level build.gradle file:
dependencies {
compile 'com.android.support:appcompat-v7:24.2.1'
//........
}
Be sure to import the correct class:
import android.support.v7.app.AlertDialog;
Then Create AlertDialog like this:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Are you sure?");
builder.setMessage("You'll lose all photos and media!");
builder.setPositiveButton("ERASE", null);
builder.setNegativeButton("CANCEL", null);
builder.show();
Section 62.2: A Basic Alert Dialog
AlertDialog.Builder builder = new AlertDialog.Builder(context);
//Set Title
builder.setTitle("Reset...")
//Set Message
.setMessage("Are you sure?")
GoalKicker.com – Android™ Notes for Professionals 455
//Set the icon of the dialog
.setIcon(drawable)
//Set the positive button, in this case, OK, which will dismiss the dialog and do
everything in the onClick method
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// Reset
}
});
AlertDialog dialog = builder.create();
//Now, any time you can call on:
dialog.show();
//So you can show the dialog.
Now this code will achieve this:
(Image source: WikiHow)
Section 62.3: ListView in AlertDialog
We can always use ListView or RecyclerView for selection from list of items, but if we have small amount of
choices and among those choices we want user to select one, we can use AlertDialog.Builder setAdapter.
private void showDialog()
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Choose any item");
final List<String> lables = new ArrayList<>();
lables.add("Item 1");
lables.add("Item 2");
lables.add("Item 3");
lables.add("Item 4");
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, lables);
builder.setAdapter(dataAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this,"You have selected " +
lables.get(which),Toast.LENGTH_LONG).show();
}
});
AlertDialog dialog = builder.create();
GoalKicker.com – Android™ Notes for Professionals 456
dialog.show();
}
Perhaps, if we don't need any particular ListView, we can use a basic way:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Select an item")
.setItems(R.array.your_array, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// The 'which' argument contains the index position of the selected item
Log.v(TAG, "Selected item on position " + which);
}
});
builder.create().show();
Section 62.4: Custom Alert Dialog with EditText
void alertDialogDemo() {
// get alert_dialog.xml view
LayoutInflater li = LayoutInflater.from(getApplicationContext());
View promptsView = li.inflate(R.layout.alert_dialog, null);
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
getApplicationContext());
// set alert_dialog.xml to alertdialog builder
alertDialogBuilder.setView(promptsView);
final EditText userInput = (EditText) promptsView.findViewById(R.id.etUserInput);
// set dialog message
alertDialogBuilder
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// get user input and set it to result
// edit text
Toast.makeText(getApplicationContext(), "Entered:
"+userInput.getText().toString(), Toast.LENGTH_LONG).show();
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
Xml file: res/layout/alert_dialog.xml
<TextView
android:id="@+id/textView1"
GoalKicker.com – Android™ Notes for Professionals 457
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Type Your Message : "
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/etUserInput"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<requestFocus />
</EditText>
Section 62.5: DatePickerDialog
DatePickerDialog is the simplest way to use DatePicker, because you can show dialog anywhere in your app. You
don't have to implement your own layout with DatePicker widget.
How to show dialog:
DatePickerDialog datePickerDialog = new DatePickerDialog(context, listener, year, month, day);
datePickerDialog.show();
You can get DataPicker widget from dialog above, to get access to more functions, and for example set minimum
date in milliseconds:
DatePicker datePicker = datePickerDialog.getDatePicker();
datePicker.setMinDate(System.currentTimeMillis());
Section 62.6: DatePicker
DatePicker allows user to pick date. When we create new instance of DatePicker, we can set initial date. If we don't
set initial date, current date will be set by default.
We can show DatePicker to user by using DatePickerDialog or by creating our own layout with DatePicker
widget.
Also we can limit range of date, which user can pick.
By setting minimum date in milliseconds
//In this case user can pick date only from future
GoalKicker.com – Android™ Notes for Professionals 458
datePicker.setMinDate(System.currentTimeMillis());
By setting maximum date in milliseconds
//In this case user can pick date only, before following week.
datePicker.setMaxDate(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7));
To receive information, about which date was picked by user, we have to use Listener.
If we are using DatePickerDialog, we can set OnDateSetListener in constructor when we are creating new
instance of DatePickerDialog:
Sample use of DatePickerDialog
public class SampleActivity extends AppCompatActivity implements DatePickerDialog.OnDateSetListener
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
private void showDatePicker() {
//We need calendar to set current date as initial date in DatePickerDialog.
Calendar calendar = new GregorianCalendar(Locale.getDefault());
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
DatePickerDialog datePickerDialog = new DatePickerDialog(this, this, year, month, day);
datePickerDialog.show();
}
@Override
public void onDateSet(DatePicker datePicker, int year, int month, int day) {
}
}
Otherwise, if we are creating our own layout with DatePicker widget, we also have to create our own listener as it
was shown in other example
Section 62.7: Alert Dialog
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
MainActivity.this);
alertDialogBuilder.setTitle("Title Dialog");
alertDialogBuilder
.setMessage("Message Dialog")
.setCancelable(true)
.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int arg1) {
// Handle Positive Button
GoalKicker.com – Android™ Notes for Professionals 459
}
})
.setNegativeButton("No",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int arg1) {
// Handle Negative Button
dialog.cancel();
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
Section 62.8: Alert Dialog with Multi-line Title
The setCustomTitle() method of AlertDialog.Builder lets you specify an arbitrary view to be used for the dialog title.
One common use for this method is to build an alert dialog that has a long title.
AlertDialog.Builder builder = new AlertDialog.Builder(context, Theme_Material_Light_Dialog);
builder.setCustomTitle(inflate(context, R.layout.my_dialog_title, null))
.setView(inflate(context, R.layout.my_dialog, null))
.setPositiveButton("OK", null);
Dialog dialog = builder.create();
dialog.show();
my_dialog_title.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
style="@android:style/TextAppearance.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur
tincidunt condimentum tristique. Vestibulum ante ante, pretium porttitor
iaculis vitae, congue ut sem. Curabitur ac feugiat ligula. Nulla
tincidunt est eu sapien iaculis rhoncus. Mauris eu risus sed justo
pharetra semper faucibus vel velit."
android:textStyle="bold"/>
</LinearLayout>
my_dialog.xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
GoalKicker.com – Android™ Notes for Professionals 460
android:orientation="vertical"
android:padding="16dp"
android:scrollbars="vertical">
<TextView
style="@android:style/TextAppearance.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="Hello world!"/>
<TextView
style="@android:style/TextAppearance.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="Hello world again!"/>
<TextView
style="@android:style/TextAppearance.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="Hello world again!"/>
<TextView
style="@android:style/TextAppearance.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="Hello world again!"/>
</LinearLayout>
</ScrollView>
GoalKicker.com – Android™ Notes for Professionals 461
Section 62.9: Date Picker within DialogFragment
xml of the Dialog:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<DatePicker
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/datePicker"
android:layout_gravity="center_horizontal"
android:calendarViewShown="false"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ACCEPT"
android:id="@+id/buttonAccept" />
</LinearLayout>
Dialog Class:
public class ChooseDate extends DialogFragment implements View.OnClickListener {
private DatePicker datePicker;
private Button acceptButton;
private boolean isDateSetted = false;
private int year;
GoalKicker.com – Android™ Notes for Professionals 462
private int month;
private int day;
private DateListener listener;
public interface DateListener {
onDateSelected(int year, int month, int day);
}
public ChooseDate(){}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.dialog_year_picker, container);
getDialog().setTitle(getResources().getString("TITLE"));
datePicker = (DatePicker) rootView.findViewById(R.id.datePicker);
acceptButton = (Button) rootView.findViewById(R.id.buttonAccept);
acceptButton.setOnClickListener(this);
if (isDateSetted) {
datePicker.updateDate(year, month, day);
}
return rootView;
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.buttonAccept:
int year = datePicker.getYear();
int month = datePicker.getMonth() + 1; // months start in 0
int day = datePicker.getDayOfMonth();
listener.onDateSelected(year, month, day);
break;
}
this.dismiss();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (DateListener) context;
}
public void setDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
this.isDateSetted = true;
}
}
Activity calling the dialog:
GoalKicker.com – Android™ Notes for Professionals 463
public class MainActivity extends AppCompatActivity implements ChooseDate.DateListener{
private int year;
private int month;
private int day;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
private void showDateDialog();
}
private void showDateDialog(){
ChooseDate pickDialog = new ChooseDate();
// We could set a date
// pickDialog.setDate(23, 10, 2016);
pickDialog.show(getFragmentManager(), "");
}
@Override
onDateSelected(int year, int month, int day){
this.day = day;
this.month = month;
this.year = year;
}
}
Section 62.10: Fullscreen Custom Dialog with no background
and no title
in styles.xml add your custom style:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppBaseTheme" parent="@android:style/Theme.Light.NoTitleBar.Fullscreen">
</style>
</resources>
Create your custom layout for the dialog: fullscreen.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</RelativeLayout>
Then in java file you can use it for an Activity or Dialog etc:
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
public class FullscreenActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GoalKicker.com – Android™ Notes for Professionals 464
//You can set no content for the activity.
Dialog mDialog = new Dialog(this, R.style.AppBaseTheme);
mDialog.setContentView(R.layout.fullscreen);
mDialog.show();
}
}
GoalKicker.com – Android™ Notes for Professionals 465
Chapter 63: Enhancing Alert Dialogs
This topic is about enhancing an AlertDialog with additional features.
Section 63.1: Alert dialog containing a clickable link
In order to show an alert dialog containing a link which can be opened by clicking it, you can use the following code:
AlertDialog.Builder builder1 = new AlertDialog.Builder(youractivity.this);
builder1.setMessage(Html.fromHtml("your message,<a href=\"http://www.google.com\">link</a>"));
builder1.setCancelable(false);
builder1.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
AlertDialog Alert1 = builder1.create();
Alert1 .show();
((TextView)Alert1.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInsta
nce());
GoalKicker.com – Android™ Notes for Professionals 466
Chapter 64: Animated AlertDialog Box
Animated Alert Dialog Which display with some animation effects.. You Can Get Some Animation for dialog box like
Fadein, Slideleft, Slidetop, SlideBottom, Slideright, Fall, Newspager, Fliph, Flipv, RotateBottom, RotateLeft, Slit,
Shake, Sidefill to make Your application attractive..
Section 64.1: Put Below code for Animated dialog..
animated_android_dialog_box.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#1184be"
android:onClick="animatedDialog1"
android:text="Animated Fall Dialog"
android:textColor="#fff" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:background="#1184be"
android:onClick="animatedDialog2"
android:text="Animated Material Flip Dialog"
android:textColor="#fff" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#1184be"
android:onClick="animatedDialog3"
android:text="Animated Material Shake Dialog"
android:textColor="#fff" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:background="#1184be"
android:onClick="animatedDialog4"
android:text="Animated Slide Top Dialog"
android:textColor="#fff" />
AnimatedAndroidDialogExample.java
public class AnimatedAndroidDialogExample extends AppCompatActivity {
NiftyDialogBuilder materialDesignAnimatedDialog;
@Override
GoalKicker.com – Android™ Notes for Professionals 467
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animated_android_dialog_box);
materialDesignAnimatedDialog = NiftyDialogBuilder.getInstance(this);
}
public void animatedDialog1(View view) {
materialDesignAnimatedDialog
.withTitle("Animated Fall Dialog Title")
.withMessage("Add your dialog message here. Animated dialog description place.")
.withDialogColor("#FFFFFF")
.withButton1Text("OK")
.withButton2Text("Cancel")
.withDuration(700)
.withEffect(Effectstype.Fall)
.show();
}
public void animatedDialog2(View view) {
materialDesignAnimatedDialog
.withTitle("Animated Flip Dialog Title")
.withMessage("Add your dialog message here. Animated dialog description place.")
.withDialogColor("#1c90ec")
.withButton1Text("OK")
.withButton2Text("Cancel")
.withDuration(700)
.withEffect(Effectstype.Fliph)
.show();
}
public void animatedDialog3(View view) {
materialDesignAnimatedDialog
.withTitle("Animated Shake Dialog Title")
.withMessage("Add your dialog message here. Animated dialog description place.")
.withDialogColor("#1c90ec")
.withButton1Text("OK")
.withButton2Text("Cancel")
.withDuration(700)
.withEffect(Effectstype.Shake)
.show();
}
public void animatedDialog4(View view) {
materialDesignAnimatedDialog
.withTitle("Animated Slide Top Dialog Title")
.withMessage("Add your dialog message here. Animated dialog description place.")
.withDialogColor("#1c90ec")
.withButton1Text("OK")
.withButton2Text("Cancel")
.withDuration(700)
.withEffect(Effectstype.Slidetop)
.show();
}
}
Add the below lines in your build.gradle to include the NifyBuilder(CustomView)
build.gradle
dependencies {
GoalKicker.com – Android™ Notes for Professionals 468
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.github.sd6352051.niftydialogeffects:niftydialogeffects:1.0.0@aar'
}
Reference Link : https://github.com/sd6352051/NiftyDialogEffects
GoalKicker.com – Android™ Notes for Professionals 469
Chapter 65: GreenDAO
GreenDAO is an Object-Relational Mapping library to help developers use SQLite databases for persistent local
storage.
Section 65.1: Helper methods for SELECT, INSERT, DELETE,
UPDATE queries
This example shows a helper class that contains methods useful, when executing the queries for data. Every
method here uses Java Generic's in order to be very flexible.
public <T> List<T> selectElements(AbstractDao<T, ?> dao) {
if (dao == null) {
return null;
}
QueryBuilder<T> qb = dao.queryBuilder();
return qb.list();
}
public <T> void insertElements(AbstractDao<T, ?> absDao, List<T> items) {
if (items == null || items.size() == 0 || absDao == null) {
return;
}
absDao.insertOrReplaceInTx(items);
}
public <T> T insertElement(AbstractDao<T, ?> absDao, T item) {
if (item == null || absDao == null) {
return null;
}
absDao.insertOrReplaceInTx(item);
return item;
}
public <T> void updateElements(AbstractDao<T, ?> absDao, List<T> items) {
if (items == null || items.size() == 0 || absDao == null) {
return;
}
absDao.updateInTx(items);
}
public <T> T selectElementByCondition(AbstractDao<T, ?> absDao,
WhereCondition... conditions) {
if (absDao == null) {
return null;
}
QueryBuilder<T> qb = absDao.queryBuilder();
for (WhereCondition condition : conditions) {
qb = qb.where(condition);
}
List<T> items = qb.list();
return items != null && items.size() > 0 ? items.get(0) : null;
}
public <T> List<T> selectElementsByCondition(AbstractDao<T, ?> absDao,
WhereCondition... conditions) {
if (absDao == null) {
return null;
}
GoalKicker.com – Android™ Notes for Professionals 470
QueryBuilder<T> qb = absDao.queryBuilder();
for (WhereCondition condition : conditions) {
qb = qb.where(condition);
}
List<T> items = qb.list();
return items != null ? items : null;
}
public <T> List<T> selectElementsByConditionAndSort(AbstractDao<T, ?> absDao,
Property sortProperty,
String sortStrategy,
WhereCondition... conditions) {
if (absDao == null) {
return null;
}
QueryBuilder<T> qb = absDao.queryBuilder();
for (WhereCondition condition : conditions) {
qb = qb.where(condition);
}
qb.orderCustom(sortProperty, sortStrategy);
List<T> items = qb.list();
return items != null ? items : null;
}
public <T> List<T> selectElementsByConditionAndSortWithNullHandling(AbstractDao<T, ?> absDao,
Property sortProperty,
boolean handleNulls,
String sortStrategy,
WhereCondition... conditions) {
if (!handleNulls) {
return selectElementsByConditionAndSort(absDao, sortProperty, sortStrategy, conditions);
}
if (absDao == null) {
return null;
}
QueryBuilder<T> qb = absDao.queryBuilder();
for (WhereCondition condition : conditions) {
qb = qb.where(condition);
}
qb.orderRaw("(CASE WHEN " + "T." + sortProperty.columnName + " IS NULL then 1 ELSE 0 END)," +
"T." + sortProperty.columnName + " " + sortStrategy);
List<T> items = qb.list();
return items != null ? items : null;
}
public <T, V extends Class> List<T> selectByJoin(AbstractDao<T, ?> absDao,
V className,
Property property, WhereCondition whereCondition)
{
QueryBuilder<T> qb = absDao.queryBuilder();
qb.join(className, property).where(whereCondition);
return qb.list();
}
public <T> void deleteElementsByCondition(AbstractDao<T, ?> absDao,
WhereCondition... conditions) {
if (absDao == null) {
return;
}
QueryBuilder<T> qb = absDao.queryBuilder();
for (WhereCondition condition : conditions) {
qb = qb.where(condition);
GoalKicker.com – Android™ Notes for Professionals 471
}
List<T> list = qb.list();
absDao.deleteInTx(list);
}
public <T> T deleteElement(DaoSession session, AbstractDao<T, ?> absDao, T object) {
if (absDao == null) {
return null;
}
absDao.delete(object);
session.clear();
return object;
}
public <T, V extends Class> void deleteByJoin(AbstractDao<T, ?> absDao,
V className,
Property property, WhereCondition whereCondition) {
QueryBuilder<T> qb = absDao.queryBuilder();
qb.join(className, property).where(whereCondition);
qb.buildDelete().executeDeleteWithoutDetachingEntities();
}
public <T> void deleteAllFromTable(AbstractDao<T, ?> absDao) {
if (absDao == null) {
return;
}
absDao.deleteAll();
}
public <T> long countElements(AbstractDao<T, ?> absDao) {
if (absDao == null) {
return 0;
}
return absDao.count();
}
Section 65.2: Creating an Entity with GreenDAO 3.X that has a
Composite Primary Key
When creating a model for a table that has a composite primary key, additional work is required on the Object for
the model Entity to respect those constraints.
The following example SQL table and Entity demonstrates the structure to store a review left by a customer for an
item in an online store. In this example, we want the customer_id and item_id columns to be a composite primary
key, allowing only one review to exist between a specific customer and item.
SQL Table
CREATE TABLE review (
customer_id STRING NOT NULL,
item_id STRING NOT NULL,
star_rating INTEGER NOT NULL,
content STRING,
PRIMARY KEY (customer_id, item_id)
);
Usually we would use the @Id and @Unique annotations above the respective fields in the entity class, however for a
composite primary key we do the following:
GoalKicker.com – Android™ Notes for Professionals 472
1. Add the @Index annotation inside the class-level @Entity annotation. The value property contains a commadelimited
list of the fields that make up the key. Use the unique property as shown to enforce uniqueness on
the key.
2. GreenDAO requires every Entity have a long or Long object as a primary key. We still need to add this to the
Entity class, however we do not need to use it or worry about it affecting our implementation. In the example
below it is called localID
Entity
@Entity(indexes = { @Index(value = "customer_id,item_id", unique = true)})
public class Review {
@Id(autoincrement = true)
private Long localID;
private String customer_id;
private String item_id;
@NotNull
private Integer star_rating;
private String content;
public Review() {}
}
Section 65.3: Getting started with GreenDao v3.X
After adding the GreenDao library dependency and Gradle plugin, we need to first create an entity object.
Entity
An entity is a Plain Old Java Object (POJO) that models some data in the database. GreenDao will use this class to
create a table in the SQLite database and automatically generate helper classes we can use to access and store data
without having to write SQL statements.
@Entity
public class Users {
@Id(autoincrement = true)
private Long id;
private String firstname;
private String lastname;
@Unique
private String email;
// Getters and setters for the fields...
}
One-time GreenDao setup
Each time an application is launched GreenDao needs to be initialized. GreenDao suggests keeping this code in an
Application class or somewhere it will only be run once.
GoalKicker.com – Android™ Notes for Professionals 473
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "mydatabase", null);
db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
DaoSession daoSession = daoMaster.newSession();
GreenDao Helper Classes
After the entity object is created, GreenDao automatically creates the helper classes used to interact with the
database. These are named similarly to the name of the entity object that was created, followed by Dao and are
retrieved from the daoSession object.
UsersDao usersDao = daoSession.getUsersDao();
Many typical database actions can now be performed using this Dao object with the entity object.
Query
String email = "jdoe@example.com";
String firstname = "John";
// Single user query WHERE email matches "jdoe@example.com"
Users user = userDao.queryBuilder()
.where(UsersDao.Properties.Email.eq(email)).build().unique();
// Multiple user query WHERE firstname = "John"
List<Users> user = userDao.queryBuilder()
.where(UsersDao.Properties.Firstname.eq(firstname)).build().list();
Insert
Users newUser = new User("John","Doe","jdoe@example.com");
usersDao.insert(newUser);
Update
// Modify a previously retrieved user object and update
user.setLastname("Dole");
usersDao.update(user);
Delete
// Delete a previously retrieved user object
usersDao.delete(user);
GoalKicker.com – Android™ Notes for Professionals 474
Chapter 66: Tools Attributes
Section 66.1: Designtime Layout Attributes
These attributes are used when the layout is rendered in Android Studio, but have no impact on the runtime.
In general you can use any Android framework attribute, just using the tools: namespace rather than the
android: namespace for layout preview. You can add both the android: namespace attribute (which is used at
runtime) and the matching tools: attribute (which overrides the runtime attribute in the layout preview only).
Just define the tools namespace as described in the remarks section.
For example the text attribute:
<EditText
tools:text="My Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Or the visibility attribute to unset a view for preview:
<LinearLayout
android:id="@+id/ll1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:visibility="gone" />
Or the context attribute to associate the layout with activity or fragment
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
Or the showIn attribute to see and included layout preview in another layout
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text"
tools:showIn="@layout/activity_main" />
GoalKicker.com – Android™ Notes for Professionals 475
Chapter 67: Formatting Strings
Section 67.1: Format a string resource
You can add wildcards in string resources and populate them at runtime:
1. Edit strings.xml
<string name="my_string">This is %1$s</string>
2. Format string as needed
String fun = "fun";
context.getString(R.string.my_string, fun);
Section 67.2: Formatting data types to String and vise versa
Data types to string formatting
Data types like int, float, double, long, boolean can be formatted to string using String.valueOf().
String.valueOf(1); //Output -> "1"
String.valueOf(1.0); //Output -> "1.0"
String.valueOf(1.2345); //Output -> "1.2345"
String.valueOf(true); //Output -> "true"
Vise versa of this, formatting string to other data type
Integer.parseInt("1"); //Output -> 1
Float.parseFloat("1.2"); //Output -> 1.2
Boolean.parseBoolean("true"); //Output -> true
Section 67.3: Format a timestamp to string
For full description of patterns, see SimpleDateFormat reference
Date now = new Date();
long timestamp = now.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy", Locale.US);
String dateStr = sdf.format(timestamp);
GoalKicker.com – Android™ Notes for Professionals 476
Chapter 68: SpannableString
Section 68.1: Add styles to a TextView
In the following example, we create an Activity to display a single TextView.
The TextView will use a SpannableString as its content, which will illustrate some of the available styles.
Here' what we're gonna do with the text :
Make it larger
Bold
Underline
Italicize
Strike-through
Colored
Highlighted
Show as superscript
Show as subscript
Show as a link
Make it clickable.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SpannableString styledString
= new SpannableString("Large\n\n" // index 0 - 5
+ "Bold\n\n" // index 7 - 11
+ "Underlined\n\n" // index 13 - 23
+ "Italic\n\n" // index 25 - 31
+ "Strikethrough\n\n" // index 33 - 46
+ "Colored\n\n" // index 48 - 55
+ "Highlighted\n\n" // index 57 - 68
+ "K Superscript\n\n" // "Superscript" index 72 - 83
+ "K Subscript\n\n" // "Subscript" index 87 - 96
+ "Url\n\n" // index 98 - 101
+ "Clickable\n\n"); // index 103 - 112
// make the text twice as large
styledString.setSpan(new RelativeSizeSpan(2f), 0, 5, 0);
// make text bold
styledString.setSpan(new StyleSpan(Typeface.BOLD), 7, 11, 0);
// underline text
styledString.setSpan(new UnderlineSpan(), 13, 23, 0);
// make text italic
styledString.setSpan(new StyleSpan(Typeface.ITALIC), 25, 31, 0);
styledString.setSpan(new StrikethroughSpan(), 33, 46, 0);
// change text color
styledString.setSpan(new ForegroundColorSpan(Color.GREEN), 48, 55, 0);
// highlight text
styledString.setSpan(new BackgroundColorSpan(Color.CYAN), 57, 68, 0);
GoalKicker.com – Android™ Notes for Professionals 477
// superscript
styledString.setSpan(new SuperscriptSpan(), 72, 83, 0);
// make the superscript text smaller
styledString.setSpan(new RelativeSizeSpan(0.5f), 72, 83, 0);
// subscript
styledString.setSpan(new SubscriptSpan(), 87, 96, 0);
// make the subscript text smaller
styledString.setSpan(new RelativeSizeSpan(0.5f), 87, 96, 0);
// url
styledString.setSpan(new URLSpan("http://www.google.com"), 98, 101, 0);
// clickable text
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
// We display a Toast. You could do anything you want here.
Toast.makeText(SpanExample.this, "Clicked", Toast.LENGTH_SHORT).show();
}
};
styledString.setSpan(clickableSpan, 103, 112, 0);
// Give the styled string to a TextView
TextView textView = new TextView(this);
// this step is mandated for the url and clickable styles.
textView.setMovementMethod(LinkMovementMethod.getInstance());
// make it neat
textView.setGravity(Gravity.CENTER);
textView.setBackgroundColor(Color.WHITE);
textView.setText(styledString);
setContentView(textView);
}
GoalKicker.com – Android™ Notes for Professionals 478
And the result will look like this:
Section 68.2: Multi string , with multi color
Method: setSpanColor
public Spanned setSpanColor(String string, int color){
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString ss = new SpannableString(string);
ss.setSpan(new ForegroundColorSpan(color), 0, string.length(), 0);
builder.append(ss);
return ss;
}
GoalKicker.com – Android™ Notes for Professionals 479
Usage:
String a = getString(R.string.string1);
String b = getString(R.string.string2);
Spanned color1 = setSpanColor(a,Color.CYAN);
Spanned color2 = setSpanColor(b,Color.RED);
Spanned mixedColor = TextUtils.concat(color1, " ", color2);
// Now we use `mixedColor`
GoalKicker.com – Android™ Notes for Professionals 480
Chapter 69: Notifications
Section 69.1: Heads Up Notification with Ticker for older
devices
Here is how to make a Heads Up Notification for capable devices, and use a Ticker for older devices.
// Tapping the Notification will open up MainActivity
Intent i = new Intent(this, MainActivity.class);
// an action to use later
// defined as an app constant:
// public static final String MESSAGE_CONSTANT = "com.example.myapp.notification";
i.setAction(MainActivity.MESSAGE_CONSTANT);
// you can use extras as well
i.putExtra("some_extra", "testValue");
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent notificationIntent = PendingIntent.getActivity(this, 999, i,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this.getApplicationContext());
builder.setContentIntent(notificationIntent);
builder.setAutoCancel(true);
builder.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
android.R.drawable.ic_menu_view));
builder.setSmallIcon(android.R.drawable.ic_dialog_map);
builder.setContentText("Test Message Text");
builder.setTicker("Test Ticker Text");
builder.setContentTitle("Test Message Title");
// set high priority for Heads Up Notification
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
// It won't show "Heads Up" unless it plays a sound
if (Build.VERSION.SDK_INT >= 21) builder.setVibrate(new long[0]);
NotificationManager mNotificationManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(999, builder.build());
Here is what it looks like on Android Marshmallow with the Heads Up Notification:
GoalKicker.com – Android™ Notes for Professionals 481
Here is what it looks like on Android KitKat with the Ticker:
GoalKicker.com – Android™ Notes for Professionals 482
On all Android versions, the Notification is shown in the notification drawer.
Android 6.0 Marshmallow:
GoalKicker.com – Android™ Notes for Professionals 483
Android 4.4.x KitKat:
GoalKicker.com – Android™ Notes for Professionals 484
Section 69.2: Creating a simple Notification
This example shows how to create a simple notification that starts an application when the user clicks it.
Specify the notification's content:
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher) // notification icon
.setContentTitle("Simple notification") // title
.setContentText("Hello word") // body message
.setAutoCancel(true); // clear notification when clicked
Create the intent to fire on click:
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
mBuilder.setContentIntent(pi);
Finally, build the notification and show it
NotificationManager mNotificationManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(0, mBuilder.build());
Section 69.3: Set custom notification - show full content text
If you want have a long text to display in the context, you need to set a custom content.
GoalKicker.com – Android™ Notes for Professionals 485
For example, you have this:
But you wish your text will be fully shown:
All you need to do, is to add a style to your content like below:
private void generateNotification(Context context) {
String message = "This is a custom notification with a very very very very very very very
very very very long text";
Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
android.R.drawable.ic_dialog_alert);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setContentTitle("Title").setContentText(message)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setLargeIcon(largeIcon)
.setAutoCancel(true)
.setWhen(System.currentTimeMillis())
.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
Notification notification = builder.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(101, notification);
}
Section 69.4: Dynamically getting the correct pixel size for
the large icon
If you're creating an image, decoding an image, or resizing an image to fit the large notification image area, you can
get the correct pixel dimensions like so:
Resources resources = context.getResources();
int width = resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
int height = resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
Section 69.5: Ongoing notification with Action button
// Cancel older notification with same id,
NotificationManager notificationMgr =
GoalKicker.com – Android™ Notes for Professionals 486
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationMgr.cancel(CALL_NOTIFY_ID);// any constant value
// Create Pending Intent,
Intent notificationIntent = null;
PendingIntent contentIntent = null;
notificationIntent = new Intent (context, YourActivityName);
contentIntent = PendingIntent.getActivity(context, 0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Notification builder
builder = new NotificationCompat.Builder(context);
builder.setContentText("Ongoing Notification..");
builder.setContentTitle("ongoing notification sample");
builder.setSmallIcon(R.drawable.notification_icon);
builder.setUsesChronometer(true);
builder.setDefaults(Notification.DEFAULT_LIGHTS);
builder.setContentIntent(contentIntent);
builder.setOngoing(true);
// Add action button in the notification
Intent intent = new Intent("action.name");
PendingIntent pIntent = PendingIntent.getBroadcast(context, 1, intent, 0);
builder.addAction(R.drawable.action_button_icon, "Action button name",pIntent);
// Notify using notificationMgr
Notification finalNotification = builder.build();
notificationMgr.notify(CALL_NOTIFY_ID, finalNotification);
Register a broadcast receiver for the same action to handle action button click event.
Section 69.6: Setting Dierent priorities in notification
NotificationCompat.Builder mBuilder =
(NotificationCompat.Builder) new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.some_small_icon)
.setContentTitle("Title")
.setContentText("This is a test notification with MAX priority")
.setPriority(Notification.PRIORITY_MAX);
When notification contains image and you want to auto expand image when notification received use
"PRIORITY_MAX", you can use other priority levels as per requirments
Different Priority Levels Info:
PRIORITY_MAX -- Use for critical and urgent notifications that alert the user to a condition that is time-critical or
needs to be resolved before they can continue with a particular task.
PRIORITY_HIGH -- Use primarily for important communication, such as message or chat events with content that is
particularly interesting for the user. High-priority notifications trigger the heads-up notification display.
PRIORITY_DEFAULT -- Use for all notifications that don't fall into any of the other priorities described here.
PRIORITY_LOW -- Use for notifications that you want the user to be informed about, but that are less urgent. Lowpriority
notifications tend to show up at the bottom of the list, which makes them a good choice for things like
GoalKicker.com – Android™ Notes for Professionals 487
public or undirected social updates: The user has asked to be notified about them, but these notifications should
never take precedence over urgent or direct communication.
PRIORITY_MIN -- Use for contextual or background information such as weather information or contextual location
information. Minimum-priority notifications do not appear in the status bar. The user discovers them on expanding
the notification shade.
References: Material Design Guidelines - notifications
Section 69.7: Set custom notification icon using `Picasso`
library
PendingIntent pendingIntent = PendingIntent.getActivity(context,
uniqueIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.remote_view_notification);
remoteViews.setImageViewResource(R.id.remoteview_notification_icon,
R.mipmap.ic_navigation_favorites);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.mipmap.ic_navigation_favorites) //just dummy icon
.setContent(remoteViews) // here we apply our view
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
final Notification notification = notificationBuilder.build();
if (android.os.Build.VERSION.SDK_INT >= 16) {
notification.bigContentView = remoteViews;
}
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(uniqueIntentId, notification);
//don't forget to include picasso to your build.gradle file.
Picasso.with(context)
.load(avatar)
.into(remoteViews, R.id.remoteview_notification_icon, uniqueIntentId, notification);
And then define a layout inside your layouts folder:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="vertical">
<ImageView
GoalKicker.com – Android™ Notes for Professionals 488
android:id="@+id/remoteview_notification_icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginRight="2dp"
android:layout_weight="0"
android:scaleType="centerCrop"/>
</LinearLayout>
Section 69.8: Scheduling notifications
Sometimes it is required to display a notification at a specific time, a task that unfortunately is not trivial on the
Android system, as there is no method setTime() or similiar for notifications. This example outlines the steps
needed to schedule notifications using the AlarmManager:
1. Add a BroadcastReceiver that listens to Intents broadcasted by the Android AlarmManager.
This is the place where you build your notification based on the extras provided with the Intent:
public class NotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Build notification based on Intent
Notification notification = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notification_small_icon)
.setContentTitle(intent.getStringExtra("title", ""))
.setContentText(intent.getStringExtra("text", ""))
.build();
// Show notification
NotificationManager manager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(42, notification);
}
}
2. Register the BroadcastReceiver in your AndroidManifest.xml file (otherwise the receiver won't receive any
Intents from the AlarmManager):
<receiver
android:name=".NotificationReceiver"
android:enabled="true" />
3. Schedule a notification by passing a PendingIntent for your BroadcastReceiver with the needed Intent
extras to the system AlarmManager. Your BroadcastReceiver will receive the Intent once the given time has
arrived and display the notification. The following method schedules a notification:
public static void scheduleNotification(Context context, long time, String title, String
text) {
Intent intent = new Intent(context, NotificationReceiver.class);
intent.putExtra("title", title);
intent.putExtra("text", text);
PendingIntent pending = PendingIntent.getBroadcast(context, 42, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Schdedule notification
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pending);
}
GoalKicker.com – Android™ Notes for Professionals 489
Please note that the 42 above needs to be unique for each scheduled notification, otherwise the PendingIntents
will replace each other causing undesired effects!
4. Cancel a notification by rebuilding the associated PendingIntent and canceling it on the system
AlarmManager. The following method cancels a notification:
public static void cancelNotification(Context context, String title, String text) {
Intent intent = new Intent(context, NotificationReceiver.class);
intent.putExtra("title", title);
intent.putExtra("text", text);
PendingIntent pending = PendingIntent.getBroadcast(context, 42, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Cancel notification
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.cancel(pending);
}
Note that the 42 above needs to match the number from step 3!
GoalKicker.com – Android™ Notes for Professionals 490
Chapter 70: AlarmManager
Section 70.1: How to Cancel an Alarm
If you want to cancel an alarm, and you don't have a reference to the original PendingIntent used to set the alarm,
you need to recreate a PendingIntent exactly as it was when it was originally created.
An Intent is considered equal by the AlarmManager:
if their action, data, type, class, and categories are the same. This does not compare any extra data
included in the intents.
Usually the request code for each alarm is defined as a constant:
public static final int requestCode = 9999;
So, for a simple alarm set up like this:
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setAction("SomeAction");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, requestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
alarmManager.setExact(AlarmManager.RTC_WAKEUP, targetTimeInMillis, pendingIntent);
Here is how you would create a new PendingIntent reference that you can use to cancel the alarm with a new
AlarmManager reference:
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setAction("SomeAction");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, requestCode, intent,
PendingIntent.FLAG_NO_CREATE);
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
if(pendingIntent != null) {
alarmManager.cancel(pendingIntent);
}
Section 70.2: Creating exact alarms on all Android versions
With more and more battery optimizations being put into the Android system over time, the methods of the
AlarmManager have also significantly changed (to allow for more lenient timing). However, for some applications it is
still required to be as exact as possible on all Android versions. The following helper uses the most accurate
method available on all platforms to schedule a PendingIntent:
public static void setExactAndAllowWhileIdle(AlarmManager alarmManager, int type, long
triggerAtMillis, PendingIntent operation) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
alarmManager.setExactAndAllowWhileIdle(type, triggerAtMillis, operation);
} else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
alarmManager.setExact(type, triggerAtMillis, operation);
} else {
alarmManager.set(type, triggerAtMillis, operation);
}
GoalKicker.com – Android™ Notes for Professionals 491
}
Section 70.3: API23+ Doze mode interferes with
AlarmManager
Android 6 (API23) introduced Doze mode which interferes with AlarmManager. It uses certain maintenance
windows to handle alarms, so even if you used setExactAndAllowWhileIdle() you cannot make sure that your
alarm fires at the desired point of time.
You can turn this behavior off for your app using your phone's settings (Settings/General/Battery & power
saving/Battery usage/Ignore optimizations or similar)
Inside your app you can check this setting ...
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName)) {
// your app is ignoring Doze battery optimization
}
... and eventually show the respective settings dialog:
Intent intent = new Intent();
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
Section 70.4: Run an intent at a later time
1. Create a receiver. This class will receive the intent and handle it how you wish.
public class AlarmReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
// Handle intent
int reqCode = intent.getExtras().getInt("requestCode");
...
}
}
2. Give an intent to AlarmManager. This example will trigger the intent to be sent to AlarmReceiver after 1
minute.
final int requestCode = 1337;
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
am.set( AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 60000 , pendingIntent );
GoalKicker.com – Android™ Notes for Professionals 492
Chapter 71: Handler
Section 71.1: HandlerThreads and communication between
Threads
As Handlers are used to send Messages and Runnables to a Thread's message queue it's easy to implement event
based communication between multiple Threads. Every Thread that has a Looper is able to receive and process
messages. A HandlerThread is a Thread that implements such a Looper, for example the main Thread (UI Thread)
implements the features of a HandlerThread.
Creating a Handler for the current Thread
Handler handler = new Handler();
Creating a Handler for the main Thread (UI Thread)
Handler handler = new Handler(Looper.getMainLooper());
Send a Runnable from another Thread to the main Thread
new Thread(new Runnable() {
public void run() {
// this is executed on another Thread
// create a Handler associated with the main Thread
Handler handler = new Handler(Looper.getMainLooper());
// post a Runnable to the main Thread
handler.post(new Runnable() {
public void run() {
// this is executed on the main Thread
}
});
}
}).start();
Creating a Handler for another HandlerThread and sending events to it
// create another Thread
HandlerThread otherThread = new HandlerThread("name");
// create a Handler associated with the other Thread
Handler handler = new Handler(otherThread.getLooper());
// post an event to the other Thread
handler.post(new Runnable() {
public void run() {
// this is executed on the other Thread
}
});
Section 71.2: Use Handler to create a Timer (similar to
javax.swing.Timer)
This can be useful if you're writing a game or something that needs to execute a piece of code every a few seconds.
import android.os.Handler;
GoalKicker.com – Android™ Notes for Professionals 493
public class Timer {
private Handler handler;
private boolean paused;
private int interval;
private Runnable task = new Runnable () {
@Override
public void run() {
if (!paused) {
runnable.run ();
Timer.this.handler.postDelayed (this, interval);
}
}
};
private Runnable runnable;
public int getInterval() {
return interval;
}
public void setInterval(int interval) {
this.interval = interval;
}
public void startTimer () {
paused = false;
handler.postDelayed (task, interval);
}
public void stopTimer () {
paused = true;
}
public Timer (Runnable runnable, int interval, boolean started) {
handler = new Handler ();
this.runnable = runnable;
this.interval = interval;
if (started)
startTimer ();
}
}
Example usage:
Timer timer = new Timer(new Runnable() {
public void run() {
System.out.println("Hello");
}
}, 1000, true)
This code will print "Hello" every second.
Section 71.3: Using a Handler to execute code after a delayed
amount of time
Executing code after 1.5 seconds:
Handler handler = new Handler();
GoalKicker.com – Android™ Notes for Professionals 494
handler.postDelayed(new Runnable() {
@Override
public void run() {
//The code you want to run after the time is up
}
}, 1500); //the time you want to delay in milliseconds
Executing code repeatedly every 1 second:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
handler.postDelayed(this, 1000);
}
}, 1000); //the time you want to delay in milliseconds
Section 71.4: Stop handler from execution
To stop the Handler from execution remove the callback attached to it using the runnable running inside it:
Runnable my_runnable = new Runnable() {
@Override
public void run() {
// your code here
}
};
public Handler handler = new Handler(); // use 'new Handler(Looper.getMainLooper());' if you want
this handler to control something in the UI
// to start the handler
public void start() {
handler.postDelayed(my_runnable, 10000);
}
// to stop the handler
public void stop() {
handler.removeCallbacks(my_runnable);
}
// to reset the handler
public void restart() {
handler.removeCallbacks(my_runnable);
handler.postDelayed(my_runnable, 10000);
}
GoalKicker.com – Android™ Notes for Professionals 495
Chapter 72: BroadcastReceiver
BroadcastReceiver (receiver) is an Android component which allows you to register for system or application
events. All registered receivers for an event are notified by the Android runtime once this event happens.
for example, a broadcast announcing that the screen has turned off, the battery is low, or a picture was captured.
Applications can also initiate broadcasts—for example, to let other applications know that some data has been
downloaded to the device and is available for them to use.
Section 72.1: Using LocalBroadcastManager
LocalBroadcastManager is used to send Broadcast Intents within an application, without exposing them to unwanted
listeners.
Using LocalBroadcastManager is more efficient and safer than using context.sendBroadcast() directly, because you
don't need to worry about any broadcasts faked by other Applications, which may pose a security hazard.
Here is a simple example of sending and receiving local broadcasts:
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("Some Action")) {
//Do something
}
}
});
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(mContext);
manager.registerReceiver(receiver, new IntentFilter("Some Action"));
// onReceive() will be called as a result of this call:
manager.sendBroadcast(new Intent("Some Action"));//See also sendBroadcastSync
//Remember to unregister the receiver when you are done with it:
manager.unregisterReceiver(receiver);
Section 72.2: BroadcastReceiver Basics
BroadcastReceivers are used to receive broadcast Intents that are sent by the Android OS, other apps, or within the
same app.
Each Intent is created with an Intent Filter, which requires a String action. Additional information can be configured
in the Intent.
Likewise, BroadcastReceivers register to receive Intents with a particular Intent Filter. They can be registered
programmatically:
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//Your implementation goes here.
}
}, new IntentFilter("Some Action"));
GoalKicker.com – Android™ Notes for Professionals 496
or in the AndroidManifest.xml file:
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="Some Action"/>
</intent-filter>
</receiver>
To receive the Intent, set the Action to something documented by Android OS, by another app or API, or within your
own application, using sendBroadcast:
mContext.sendBroadcast(new Intent("Some Action"));
Additionally, the Intent can contain information, such as Strings, primitives, and Parcelables, that can be viewed in
onReceive.
Section 72.3: Introduction to Broadcast receiver
A Broadcast receiver is an Android component which allows you to register for system or application events.
A receiver can be registered via the AndroidManifest.xml file or dynamically via the Context.registerReceiver()
method.
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//Your implementation goes here.
}
}
Here I have taken an example of ACTION_BOOT_COMPLETED which is fired by the system once the Android has
completed the boot process.
You can register a receiver in manifest file like this:
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<receiver android:name="MyReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED">
</action>
</intent-filter>
</receiver>
</application>
Now device gets booted, onReceive() method will be called and then you can do your work (e.g. start a service,
start an alarm).
Section 72.4: Using ordered broadcasts
Ordered broadcasts are used when you need to specify a priority for broadcast listeners.
In this example firstReceiver will receive broadcast always before than a secondReceiver:
GoalKicker.com – Android™ Notes for Professionals 497
final int highPriority = 2;
final int lowPriority = 1;
final String action = "action";
// intent filter for first receiver with high priority
final IntentFilter firstFilter = new IntentFilter(action);
first Filter.setPriority(highPriority);
final BroadcastReceiver firstReceiver = new MyReceiver();
// intent filter for second receiver with low priority
final IntentFilter secondFilter = new IntentFilter(action);
secondFilter.setPriority(lowPriority);
final BroadcastReceiver secondReceiver = new MyReceiver();
// register our receivers
context.registerReceiver(firstReceiver, firstFilter);
context.registerReceiver(secondReceiver, secondFilter);
// send ordered broadcast
context.sendOrderedBroadcast(new Intent(action), null);
Furthermore broadcast receiver can abort ordered broadcast:
@Override
public void onReceive(final Context context, final Intent intent) {
abortBroadcast();
}
in this case all receivers with lower priority will not receive a broadcast message.
Section 72.5: Sticky Broadcast
If we are using method sendStickyBroadcast(intent) the corresponding intent is sticky, meaning the intent you are
sending stays around after broadcast is complete. A StickyBroadcast as the name suggests is a mechanism to read
the data from a broadcast, after the broadcast is complete. This can be used in a scenario where you may want to
check say in an Activity's onCreate() the value of a key in the intent before that Activity was launched.
Intent intent = new Intent("com.org.action");
intent.putExtra("anIntegerKey", 0);
sendStickyBroadcast(intent);
Section 72.6: Enabling and disabling a Broadcast Receiver
programmatically
To enable or disable a BroadcastReceiver, we need to get a reference to the PackageManager and we need a
ComponentName object containing the class of the receiver we want to enable/disable:
ComponentName componentName = new ComponentName(context, MyBroadcastReceiver.class);
PackageManager packageManager = context.getPackageManager();
Now we can call the following method to enable the BroadcastReceiver:
packageManager.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
GoalKicker.com – Android™ Notes for Professionals 498
Or we can instead use COMPONENT_ENABLED_STATE_DISABLED to disable the receiver:
packageManager.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
Section 72.7: Example of a LocalBroadcastManager
A BroadcastReceiver is basically a mechanism to relay Intents through the OS to perform specific actions. A classic
definition being
"A Broadcast receiver is an Android component which allows you to register for system or application
events."
LocalBroadcastManager is a way to send or receive broadcasts within an application process. This mechanism has a
lot of advantages
1. since the data remains inside the application process, the data cannot be leaked.
2. LocalBroadcasts are resolved faster, since the resolution of a normal broadcast happens at runtime
throughout the OS.
A simple example of a LocalBroastManager is:
SenderActivity
Intent intent = new Intent("anEvent");
intent.putExtra("key", "This is an event");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
ReceiverActivity
1. Register a receiver
LocalBroadcastManager.getInstance(this).registerReceiver(aLBReceiver,
new IntentFilter("anEvent"));
2. A concrete object for performing action when the receiver is called
private BroadcastReceiver aLBReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// perform action here.
}
};
3. unregister when the view is not visible any longer.
@Override
protected void onPause() {
// Unregister since the activity is about to be closed.
LocalBroadcastManager.getInstance(this).unregisterReceiver(aLBReceiver);
super.onDestroy();
}
GoalKicker.com – Android™ Notes for Professionals 499
Section 72.8: Android stopped state
Starting with Android 3.1 all applications, upon installation, are placed in a stopped state. While in stopped state,
the application will not run for any reason, except by a manual launch of an activity, or an explicit intent that
addresses an activity ,service or broadcast.
When writing system app that installs APKs directly, please take into account that the newly installed APP won't
receive any broadcasts until moved into a non stopped state.
An easy way to to activate an app is to sent a explicit broadcast to this app. as most apps implement
INSTALL_REFERRER, we can use it as a hooking point
Scan the manifest of the installed app, and send an explicit broadcast to to each receiver:
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.setComponent(new ComponentName(packageName, fullClassName));
sendBroadcast(intent);
Section 72.9: Communicate two activities through custom
Broadcast receiver
You can communicate two activities so that Activity A can be notified of an event happening in Activity B.
Activity A
final String eventName = "your.package.goes.here.EVENT";
@Override
protected void onCreate(Bundle savedInstanceState) {
registerEventReceiver();
super.onCreate(savedInstanceState);
}
@Override
protected void onDestroy() {
unregisterEventReceiver(eventReceiver);
super.onDestroy();
}
private void registerEventReceiver() {
IntentFilter eventFilter = new IntentFilter();
eventFilter.addAction(eventName);
registerReceiver(eventReceiver, eventFilter);
}
private BroadcastReceiver eventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//This code will be executed when the broadcast in activity B is launched
}
};
Activity B
final String eventName = "your.package.goes.here.EVENT";
private void launchEvent() {
GoalKicker.com – Android™ Notes for Professionals 500
Intent eventIntent = new Intent(eventName);
this.sendBroadcast(eventIntent);
}
Of course you can add more information to the broadcast adding extras to the Intent that is passed between the
activities. Not added to keep the example as simple as possible.
Section 72.10: BroadcastReceiver to handle
BOOT_COMPLETED events
Example below shows how to create a BroadcastReceiver which is able to receive BOOT_COMPLETED events. This
way, you are able to start a Service or start an Activity as soon device was powered up.
Also, you can use BOOT_COMPLETED events to restore your alarms since they are destroyed when device is powered
off.
NOTE: The user needs to have started the application at least once before you can receive the BOOT_COMPLETED
action.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.example" >
...
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
<application>
...
<receiver android:name="com.test.example.MyCustomBroadcastReceiver">
<intent-filter>
<!-- REGISTER TO RECEIVE BOOT_COMPLETED EVENTS -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
MyCustomBroadcastReceiver.java
public class MyCustomBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action != null) {
if (action.equals(Intent.ACTION_BOOT_COMPLETED) ) {
// TO-DO: Code to handle BOOT COMPLETED EVENT
// TO-DO: I can start an service.. display a notification... start an activity
}
}
}
}
GoalKicker.com – Android™ Notes for Professionals 501
Section 72.11: Bluetooth Broadcast receiver
add permission in your manifest file
<uses-permission android:name="android.permission.BLUETOOTH" />
In your Fragment(or Activity)
Add the receiver method
private BroadcastReceiver mBluetoothStatusChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Bundle extras = intent.getExtras();
final int bluetoothState = extras.getInt(Constants.BUNDLE_BLUETOOTH_STATE);
switch(bluetoothState) {
case BluetoothAdapter.STATE_OFF:
// Bluetooth OFF
break;
case BluetoothAdapter.STATE_TURNING_OFF:
// Turning OFF
break;
case BluetoothAdapter.STATE_ON:
// Bluetooth ON
break;
case BluetoothAdapter.STATE_TURNING_ON:
// Turning ON
break;
}
};
Register broadcast
Call this method on onResume()
private void registerBroadcastManager(){
final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getActivity());
manager.registerReceiver(mBluetoothStatusChangedReceiver, new
IntentFilter(Constants.BROADCAST_BLUETOOTH_STATE));
}
Unregister broadcast
Call this method on onPause()
private void unregisterBroadcastManager(){
final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getActivity());
// Beacon
機能用
manager.unregisterReceiver(mBluetoothStatusChangedReceiver);
}
GoalKicker.com – Android™ Notes for Professionals 502
Chapter 73: UI Lifecycle
Section 73.1: Saving data on memory trimming
public class ExampleActivity extends Activity {
private final static String EXAMPLE_ARG = "example_arg";
private int mArg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example);
if(savedInstanceState != null) {
mArg = savedInstanceState.getInt(EXAMPLE_ARG);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(EXAMPLE_ARG, mArg);
}
}
Explanation
So, what is happening here?
The Android system will always strive to clear as much memory as it can. So, if your activity is down to the
background, and another foreground activity is demanding its share, the Android system will call onTrimMemory()
on your activity.
But that doesn't mean that all your properties should vanish. What you should do is to save them into a Bundle
object. Bundle object are much better handled memory wise. Inside a bundle every object is identified by unique
text sequence - in the example above integer value variable mArg is hold under reference name EXAMPLE_ARG. And
when the activity is recreated extract your old values from the Bundle object instead of recreating them from
scratch
GoalKicker.com – Android™ Notes for Professionals 503
Chapter 74: HttpURLConnection
Section 74.1: Creating an HttpURLConnection
In order to create a new Android HTTP Client HttpURLConnection, call openConnection() on a URL instance. Since
openConnection() returns a URLConnection, you need to explicitly cast the returned value.
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// do something with the connection
If you are creating a new URL, you also have to handle the exceptions associated with URL parsing.
try {
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// do something with the connection
} catch (MalformedURLException e) {
e.printStackTrace();
}
Once the response body has been read and the connection is no longer required, the connection should be closed
by calling disconnect().
Here is an example:
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
// do something with the connection
} finally {
connection.disconnect();
}
Section 74.2: Sending an HTTP GET request
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// read the input stream
// in this case, I simply read the first line of the stream
String line = br.readLine();
Log.d("HTTP-GET", line);
} finally {
connection.disconnect();
}
Please note that exceptions are not handled in the example above. A full example, including (a trivial) exception
handling, would be:
URL url;
HttpURLConnection connection = null;
GoalKicker.com – Android™ Notes for Professionals 504
try {
url = new URL("http://example.com");
connection = (HttpURLConnection) url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// read the input stream
// in this case, I simply read the first line of the stream
String line = br.readLine();
Log.d("HTTP-GET", line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
Section 74.3: Reading the body of an HTTP GET request
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// use a string builder to bufferize the response body
// read from the input strea.
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append('\n');
}
// use the string builder directly,
// or convert it into a String
String body = sb.toString();
Log.d("HTTP-GET", body);
} finally {
connection.disconnect();
}
Please note that exceptions are not handled in the example above.
Section 74.4: Sending an HTTP POST request with
parameters
Use a HashMap to store the parameters that should be sent to the server through POST parameters:
HashMap<String, String> params;
Once the params HashMap is populated, create the StringBuilder that will be used to send them to the server:
StringBuilder sbParams = new StringBuilder();
int i = 0;
for (String key : params.keySet()) {
try {
GoalKicker.com – Android™ Notes for Professionals 505
if (i != 0){
sbParams.append("&");
}
sbParams.append(key).append("=")
.append(URLEncoder.encode(params.get(key), "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
i++;
}
Then, create the HttpURLConnection, open the connection, and send the POST parameters:
try{
String url = "http://www.example.com/test.php";
URL urlObj = new URL(url);
HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setReadTimeout(10000);
conn.setConnectTimeout(15000);
conn.connect();
String paramsString = sbParams.toString();
DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
wr.writeBytes(paramsString);
wr.flush();
wr.close();
} catch (IOException e) {
e.printStackTrace();
}
Then receive the result that the server sends back:
try {
InputStream in = new BufferedInputStream(conn.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
Log.d("test", "result from server: " + result.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
GoalKicker.com – Android™ Notes for Professionals 506
Section 74.5: A multi-purpose HttpURLConnection class to
handle all types of HTTP requests
The following class can be used as a single class that can handle GET, POST, PUT, PATCH, and other requests:
class APIResponseObject{
int responseCode;
String response;
APIResponseObject(int responseCode,String response)
{
this.responseCode = responseCode;
this.response = response;
}
}
public class APIAccessTask extends AsyncTask<String,Void,APIResponseObject> {
URL requestUrl;
Context context;
HttpURLConnection urlConnection;
List<Pair<String,String>> postData, headerData;
String method;
int responseCode = HttpURLConnection.HTTP_OK;
interface OnCompleteListener{
void onComplete(APIResponseObject result);
}
public OnCompleteListener delegate = null;
APIAccessTask(Context context, String requestUrl, String method, OnCompleteListener delegate){
this.context = context;
this.delegate = delegate;
this.method = method;
try {
this.requestUrl = new URL(requestUrl);
}
catch(Exception ex){
ex.printStackTrace();
}
}
APIAccessTask(Context context, String requestUrl, String method, List<Pair<String,String>>
postData, OnCompleteListener delegate){
this(context, requestUrl, method, delegate);
this.postData = postData;
}
APIAccessTask(Context context, String requestUrl, String method, List<Pair<String,String>>
postData,
List<Pair<String,String>> headerData, OnCompleteListener delegate ){
this(context, requestUrl,method,postData,delegate);
this.headerData = headerData;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
GoalKicker.com – Android™ Notes for Professionals 507
protected APIResponseObject doInBackground(String... params) {
Log.d("debug", "url = "+ requestUrl);
try {
urlConnection = (HttpURLConnection) requestUrl.openConnection();
if(headerData != null) {
for (Pair pair : headerData) {
urlConnection.setRequestProperty(pair.first.toString(),pair.second.toString());
}
}
urlConnection.setDoInput(true);
urlConnection.setChunkedStreamingMode(0);
urlConnection.setRequestMethod(method);
urlConnection.connect();
StringBuilder sb = new StringBuilder();
if(!(method.equals("GET"))) {
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
writer.write(getPostDataString(postData));
writer.flush();
writer.close();
out.close();
}
urlConnection.connect();
responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
return new APIResponseObject(responseCode, sb.toString());
}
catch(Exception ex){
ex.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(APIResponseObject result) {
delegate.onComplete(result);
super.onPostExecute(result);
}
private String getPostDataString(List<Pair<String, String>> params) throws
UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
boolean first = true;
for(Pair<String,String> pair : params){
if (first)
first = false;
else
result.append("&");
GoalKicker.com – Android™ Notes for Professionals 508
result.append(URLEncoder.encode(pair.first,"UTF-8"));
result.append("=");
result.append(URLEncoder.encode(pair.second, "UTF-8"));
}
return result.toString();
}
}
Usage
Use any of the given constructors of the class depending on whether you need to send POST data or any extra
headers.
The onComplete() method will be called when the data fetching is complete. The data is returned as an object of
the APIResponseObject class, which has a status code stating the HTTP status code of the request and a string
containing the response. You can parse this response in your class, i.e. XML or JSON.
Call execute() on the object of the class to execute the request, as shown in the following example:
class MainClass {
String url = "https://example.com./api/v1/ex";
String method = "POST";
List<Pair<String,String>> postData = new ArrayList<>();
postData.add(new Pair<>("email","whatever");
postData.add(new Pair<>("password", "whatever");
new APIAccessTask(MainActivity.this, url, method, postData,
new APIAccessTask.OnCompleteListener() {
@Override
public void onComplete(APIResponseObject result) {
if (result.responseCode == HttpURLConnection.HTTP_OK) {
String str = result.response;
// Do your XML/JSON parsing here
}
}
}).execute();
}
Section 74.6: Use HttpURLConnection for multipart/formdata
Create custom class for calling multipart/form-data HttpURLConnection request
MultipartUtility.java
public class MultipartUtility {
private final String boundary;
private static final String LINE_FEED = "\r\n";
private HttpURLConnection httpConn;
private String charset;
private OutputStream outputStream;
private PrintWriter writer;
/**
* This constructor initializes a new HTTP POST request with content type
* is set to multipart/form-data
*
* @param requestURL
* @param charset
GoalKicker.com – Android™ Notes for Professionals 509
* @throws IOException
*/
public MultipartUtility(String requestURL, String charset)
throws IOException {
this.charset = charset;
// creates a unique boundary based on time stamp
boundary = "===" + System.currentTimeMillis() + "===";
URL url = new URL(requestURL);
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setUseCaches(false);
httpConn.setDoOutput(true); // indicates POST method
httpConn.setDoInput(true);
httpConn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + boundary);
outputStream = httpConn.getOutputStream();
writer = new PrintWriter(new OutputStreamWriter(outputStream, charset),
true);
}
/**
* Adds a form field to the request
*
* @param name field name
* @param value field value
*/
public void addFormField(String name, String value) {
writer.append("--" + boundary).append(LINE_FEED);
writer.append("Content-Disposition: form-data; name=\"" + name + "\"")
.append(LINE_FEED);
writer.append("Content-Type: text/plain; charset=" + charset).append(
LINE_FEED);
writer.append(LINE_FEED);
writer.append(value).append(LINE_FEED);
writer.flush();
}
/**
* Adds a upload file section to the request
*
* @param fieldName name attribute in <input type="file" name="..." />
* @param uploadFile a File to be uploaded
* @throws IOException
*/
public void addFilePart(String fieldName, File uploadFile)
throws IOException {
String fileName = uploadFile.getName();
writer.append("--" + boundary).append(LINE_FEED);
writer.append(
"Content-Disposition: form-data; name=\"" + fieldName
+ "\"; filename=\"" + fileName + "\"")
.append(LINE_FEED);
writer.append(
"Content-Type: "
+ URLConnection.guessContentTypeFromName(fileName))
.append(LINE_FEED);
writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
writer.append(LINE_FEED);
writer.flush();
FileInputStream inputStream = new FileInputStream(uploadFile);
byte[] buffer = new byte[4096];
GoalKicker.com – Android™ Notes for Professionals 510
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
inputStream.close();
writer.append(LINE_FEED);
writer.flush();
}
/**
* Adds a header field to the request.
*
* @param name - name of the header field
* @param value - value of the header field
*/
public void addHeaderField(String name, String value) {
writer.append(name + ": " + value).append(LINE_FEED);
writer.flush();
}
/**
* Completes the request and receives response from the server.
*
* @return a list of Strings as response in case the server returned
* status OK, otherwise an exception is thrown.
* @throws IOException
*/
public List<String> finish() throws IOException {
List<String> response = new ArrayList<String>();
writer.append(LINE_FEED).flush();
writer.append("--" + boundary + "--").append(LINE_FEED);
writer.close();
// checks server's status code first
int status = httpConn.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpConn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
response.add(line);
}
reader.close();
httpConn.disconnect();
} else {
throw new IOException("Server returned non-OK status: " + status);
}
return response;
}
}
Use it (Async way)
MultipartUtility multipart = new MultipartUtility(requestURL, charset);
// In your case you are not adding form data so ignore this
/*This is to add parameter values */
for (int i = 0; i < myFormDataArray.size(); i++) {
multipart.addFormField(myFormDataArray.get(i).getParamName(),
myFormDataArray.get(i).getParamValue());
GoalKicker.com – Android™ Notes for Professionals 511
}
//add your file here.
/*This is to add file content*/
for (int i = 0; i < myFileArray.size(); i++) {
multipart.addFilePart(myFileArray.getParamName(),
new File(myFileArray.getFileName()));
}
List<String> response = multipart.finish();
Debug.e(TAG, "SERVER REPLIED:");
for (String line : response) {
Debug.e(TAG, "Upload Files Response:::" + line);
// get your server response here.
responseString = line;
}
Section 74.7: Upload (POST) file using HttpURLConnection
Quite often it's necessary to send/upload a file to a remote server, for example, an image, video, audio or a backup
of the application database to a remote private server. Assuming the server is expecting a POST request with the
content, here's a simple example of how to complete this task in Android.
File uploads are sent using multipart/form-data POST requests. It's very easy to implement:
URL url = new URL(postTarget);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
String auth = "Bearer " + oauthToken;
connection.setRequestProperty("Authorization", basicAuth);
String boundary = UUID.randomUUID().toString();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
DataOutputStream request = new DataOutputStream(uc.getOutputStream());
request.writeBytes("--" + boundary + "\r\n");
request.writeBytes("Content-Disposition: form-data; name=\"description\"\r\n\r\n");
request.writeBytes(fileDescription + "\r\n");
request.writeBytes("--" + boundary + "\r\n");
request.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.fileName +
"\"\r\n\r\n");
request.write(FileUtils.readFileToByteArray(file));
request.writeBytes("\r\n");
request.writeBytes("--" + boundary + "--\r\n");
request.flush();
int respCode = connection.getResponseCode();
switch(respCode) {
case 200:
//all went ok - read response
...
break;
case 301:
case 302:
GoalKicker.com – Android™ Notes for Professionals 512
case 307:
//handle redirect - for example, re-post to the new location
...
break;
...
default:
//do something sensible
}
Of course, exceptions will need to be caught or declared as being thrown. A couple points to note about this code:
1. postTarget is the destination URL of the POST; oauthToken is the authentication token; fileDescription is
the description of the file, which is sent as the value of field description; file is the file to be sent - it's of
type java.io.File - if you have the file path, you can use new File(filePath) instead.
2. It sets Authorization header for an oAuth auth
3. It uses Apache Common FileUtil to read the file into a byte array - if you already have the content of the file
in a byte array or in some other way in memory, then there's no need to read it.
GoalKicker.com – Android™ Notes for Professionals 513
Chapter 75: Callback URL
Section 75.1: Callback URL example with Instagram OAuth
One of the use cases of callback URLs is OAuth. Let us do this with an Instagram Login: If the user enters their
credentials and clicks the Login button, Instagram will validate the credentials and return an access_token. We
need that access_token in our app.
For our app to be able to listen to such links, we need to add a callback URL to our Activity. We can do this by
adding an <intent-filter/> to our Activity, which will react to that callback URL. Assume that our callback URL is
appSchema://appName.com. Then you have to add the following lines to your desired Activity in the Manifest.xml
file:
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="appName.com" android:scheme="appSchema"/>
Explanation of the lines above:
<category android:name="android.intent.category.BROWSABLE"/> makes the target activity allow itself to
be started by a web browser to display data referenced by a link.
<data android:host="appName.com" android:scheme="appSchema"/> specifies our schema and host of our
callback URL.
All together, these lines will cause the specific Activity to be opened whenever the callback URL is called in a
browser.
Now, in order to get the contents of the URL in your Activity, you need to override the onResume() method as
follows:
@Override
public void onResume() {
// The following line will return "appSchema://appName.com".
String CALLBACK_URL = getResources().getString(R.string.insta_callback);
Uri uri = getIntent().getData();
if (uri != null && uri.toString().startsWith(CALLBACK_URL)) {
String access_token = uri.getQueryParameter("access_token");
}
// Perform other operations here.
}
Now you have retrieved the access_token from Instagram, that is used in various API endpoints of Instagram.
GoalKicker.com – Android™ Notes for Professionals 514
Chapter 76: Snackbar
Parameter Description
view View: The view to find a parent from.
text CharSequence: The text to show. Can be formatted text.
resId int: The resource id of the string resource to use. Can be formatted text.
duration int: How long to display the message. This can be LENGTH_SHORT, LENGTH_LONG or
LENGTH_INDEFINITE
Section 76.1: Creating a simple Snackbar
Creating a Snackbar can be done as follows:
Snackbar.make(view, "Text to display", Snackbar.LENGTH_LONG).show();
The view is used to find a suitable parent to use to display the Snackbar. Typically this would be a
CoordinatorLayout that you've defined in your XML, which enables adding functionality such as swipe to dismiss
and automatically moving of other widgets (e.g. FloatingActionButton). If there's no CoordinatorLayout then the
window decor's content view is used.
Very often we also add an action to the Snackbar. A common use case would be an "Undo" action.
Snackbar.make(view, "Text to display", Snackbar.LENGTH_LONG)
.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
// put your logic here
}
})
.show();
You can create a Snackbar and show it later:
Snackbar snackbar = Snackbar.make(view, "Text to display", Snackbar.LENGTH_LONG);
snackbar.show();
If you want to change the color of the Snackbar's text:
Snackbar snackbar = Snackbar.make(view, "Text to display", Snackbar.LENGTH_LONG);
View view = snackbar .getView();
TextView textView = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
textView.setTextColor(Color.parseColor("#FF4500"));
snackbar.show();
By default Snackbar dismisses on it's right swipe.This example demonstrates how to dismiss the snackBar on it's
left swipe.
Section 76.2: Custom Snack Bar
Function to customize snackbar
public static Snackbar makeText(Context context, String message, int duration) {
Activity activity = (Activity) context;
GoalKicker.com – Android™ Notes for Professionals 515
View layout;
Snackbar snackbar = Snackbar
.make(activity.findViewById(android.R.id.content), message, duration);
layout = snackbar.getView();
//setting background color
layout.setBackgroundColor(context.getResources().getColor(R.color.orange));
android.widget.TextView text = (android.widget.TextView)
layout.findViewById(android.support.design.R.id.snackbar_text);
//setting font color
text.setTextColor(context.getResources().getColor(R.color.white));
Typeface font = null;
//Setting font
font = Typeface.createFromAsset(context.getAssets(), "DroidSansFallbackanmol256.ttf");
text.setTypeface(font);
return snackbar;
}
Call the function from fragment or activity
SnackBar.makeText(MyActivity.this, "Please Locate your address at Map",
Snackbar.LENGTH_SHORT).show();
Section 76.3: Custom Snackbar (no need view)
Creating an Snackbar without the need pass view to Snackbar, all layout create in android in android.R.id.content.
public class CustomSnackBar {
public static final int STATE_ERROR = 0;
public static final int STATE_WARNING = 1;
public static final int STATE_SUCCESS = 2;
public static final int VIEW_PARENT = android.R.id.content;
public CustomSnackBar(View view, String message, int actionType) {
super();
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
View sbView = snackbar.getView();
TextView textView = (TextView)
sbView.findViewById(android.support.design.R.id.snackbar_text);
textView.setTextColor(Color.parseColor("#ffffff"));
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
textView.setGravity(View.TEXT_ALIGNMENT_CENTER);
textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
switch (actionType) {
case STATE_ERROR:
snackbar.getView().setBackgroundColor(Color.parseColor("#F12B2B"));
break;
case STATE_WARNING:
snackbar.getView().setBackgroundColor(Color.parseColor("#000000"));
break;
case STATE_SUCCESS:
snackbar.getView().setBackgroundColor(Color.parseColor("#7ED321"));
break;
}
snackbar.show();
}
}
GoalKicker.com – Android™ Notes for Professionals 516
for call class
new CustomSnackBar(findViewById(CustomSnackBar.VIEW_PARENT),"message", CustomSnackBar.STATE_ERROR);
Section 76.4: Snackbar with Callback
You can use Snackbar.Callback to listen if the snackbar was dismissed by user or timeout.
Snackbar.make(getView(), "Hi snackbar!", Snackbar.LENGTH_LONG).setCallback( new Snackbar.Callback()
{
@Override
public void onDismissed(Snackbar snackbar, int event) {
switch(event) {
case Snackbar.Callback.DISMISS_EVENT_ACTION:
Toast.makeText(getActivity(), "Clicked the action",
Toast.LENGTH_LONG).show();
break;
case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
Toast.makeText(getActivity(), "Time out", Toast.LENGTH_LONG).show();
break;
}
}
@Override
public void onShown(Snackbar snackbar) {
Toast.makeText(getActivity(), "This is my annoying step-brother",
Toast.LENGTH_LONG).show();
}
}).setAction("Go!", new View.OnClickListener() {
@Override
public void onClick(View v) {
}
}).show();
Section 76.5: Snackbar vs Toasts: Which one should I use?
Toasts are generally used when we want to display an information to the user regarding some action that has
successfully (or not) happened and this action does not require the user to take any other action. Like when a
message has been sent, for example:
Toast.makeText(this, "Message Sent!", Toast.LENGTH_SHORT).show();
Snackbars are also used to display an information. But this time, we can give the user an opportunity to take an
action. For example, let's say the user deleted a picture by mistake and he wants to get it back. We can provide a
Snackbar with the "Undo" action. Like this:
Snackbar.make(getCurrentFocus(), "Picture Deleted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View view) {
//Return his picture
}
})
.show();
Conclusion: Toasts are used when we don't need user interaction. Snackbars are used to allow users to take
GoalKicker.com – Android™ Notes for Professionals 517
another action or undo a previous one.
Section 76.6: Custom Snackbar
This example shows a white Snackbar with custom Undo icon.
Snackbar customBar = Snackbar.make(view , "Text to be displayed", Snackbar.LENGTH_LONG);
customBar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
//Put the logic for undo button here
}
});
View sbView = customBar.getView();
//Changing background to White
sbView.setBackgroundColor(Color.WHITE));
TextView snackText = (TextView) sbView.findViewById(android.support.design.R.id.snackbar_text);
if (snackText!=null) {
//Changing text color to Black
snackText.setTextColor(Color.BLACK);
}
TextView actionText = (TextView) sbView.findViewById(android.support.design.R.id.snackbar_action);
if (actionText!=null) {
// Setting custom Undo icon
actionText.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.custom_undo, 0, 0, 0);
}
customBar.show();
GoalKicker.com – Android™ Notes for Professionals 518
Chapter 77: Widgets
Section 77.1: Manifest Declaration -
Declare the AppWidgetProvider class in your application's AndroidManifest.xml file. For example:
<receiver android:name="ExampleAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
Section 77.2: Metadata
Add the AppWidgetProviderInfo metadata in res/xml:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>
Section 77.3: AppWidgetProvider Class
The most important AppWidgetProvider callback is onUpdate(). It is called every time an appwidget is added.
public class ExampleAppWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
// Get the layout for the App Widget and attach an on-click listener
// to the button
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
GoalKicker.com – Android™ Notes for Professionals 519
onAppWidgetOptionsChanged() is called when the widget is placed or resized.
onDeleted(Context, int[]) is called when the widget is deleted.
Section 77.4: Create/Integrate Basic Widget using Android
Studio
Latest Android Studio will create & integrate a Basic Widget to your Application in 2 steps.
Right on your Application ==> New ==> Widget ==> App Widget
.
It will show a Screen like below & fill the fields
GoalKicker.com – Android™ Notes for Professionals 520
Its Done.
It will create & integrate a basic HelloWorld Widget(Including Layout File , Meta Data File , Declaration in Manifest
File etc.) to your Application.
Section 77.5: Two widgets with dierent layouts declaration
1. Declare two receivers in a manifest file:
<receiver
android:name=".UVMateWidget"
android:label="UVMate Widget 1x1">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_1x1" />
</receiver>
<receiver
android:name=".UVMateWidget2x2"
android:label="UVMate Widget 2x2">
GoalKicker.com – Android™ Notes for Professionals 521
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x2" />
</receiver>
2. Create two layouts
@xml/widget_1x1
@xml/widget_2x2
3. Declare the subclass UVMateWidget2x2 from the UVMateWidget class with extended behavior:
package au.com.aershov.uvmate;
import android.content.Context;
import android.widget.RemoteViews;
public class UVMateWidget2x2 extends UVMateWidget {
public RemoteViews getRemoteViews(Context context, int minWidth,
int minHeight) {
mUVMateHelper.saveWidgetSize(mContext.getString(R.string.app_ws_2x2));
return new RemoteViews(context.getPackageName(), R.layout.widget_2x2);
}
}
GoalKicker.com – Android™ Notes for Professionals 522
Chapter 78: Toast
Parameter Details
context The context to display your Toast in. this is commonly used in an Activity and getActivity() is
commonly used in a Fragment
text A CharSequence that specifies what text will be shown in the Toast. Any object that implements
CharSequence can be used, including a String
resId A resource ID that can be used to provide a resource String to display in the Toast
duration Integer flag representing how long the Toast will show. Options are Toast.LENGTH_SHORT and
Toast.LENGTH_LONG
gravity Integer specifying the position, or "gravity" of the Toast. See options here
xOffset Specifies the horizontal offset for the Toast position
yOffset Specifies the vertical offset for the Toast position
A Toast provides simple feedback about an operation in a small popup and automatically disappears after a
timeout. It only fills the amount of space required for the message and the current activity remains visible and
interactive.
Section 78.1: Creating a custom Toast
If you don't want to use the default Toast view, you can provide your own using the setView(View) method on a
Toast object.
First, create the XML layout you would like to use in your Toast.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toast_layout_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:background="#111">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFF"/>
<TextView android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFF"/>
</LinearLayout>
Then, when creating your Toast, inflate your custom View from XML, and call setView
// Inflate the custom view from XML
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast_layout,
(ViewGroup) findViewById(R.id.toast_layout_root));
// Set the title and description TextViews from our custom layout
TextView title = (TextView) layout.findViewById(R.id.title);
title.setText("Toast Title");
GoalKicker.com – Android™ Notes for Professionals 523
TextView description = (TextView) layout.findViewById(R.id.description);
description.setText("Toast Description");
// Create and show the Toast object
Toast toast = new Toast(getApplicationContext());
toast.setGravity(Gravity.CENTER, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
Section 78.2: Set position of a Toast
A standard toast notification appears at the bottom of the screen aligned in horizontal centre. You can change this
position with the setGravity(int, int, int). This accepts three parameters: a Gravity constant, an x-position
offset, and a y-position offset.
For example, if you decide that the toast should appear in the top-left corner, you can set the gravity like this:
toast.setGravity(Gravity.TOP|Gravity.LEFT, 0, 0);
Section 78.3: Showing a Toast Message
In Android, a Toast is a simple UI element that can be used to give contextual feedback to a user.
To display a simple Toast message, we can do the following.
// Declare the parameters to use for the Toast
Context context = getApplicationContext();
// in an Activity, you may also use "this"
// in a fragment, you can use getActivity()
CharSequence message = "I'm an Android Toast!";
int duration = Toast.LENGTH_LONG; // Toast.LENGTH_SHORT is the other option
// Create the Toast object, and show it!
Toast myToast = Toast.makeText(context, message, duration);
myToast.show();
Or, to show a Toast inline, without holding on to the Toast object you can:
Toast.makeText(context, "Ding! Your Toast is ready.", Toast.LENGTH_SHORT).show();
IMPORTANT: Make sure that the show() method is called from the UI thread. If you're trying to show a Toast from
a different thread you can e.g. use runOnUiThread method of an Activity.
Failing to do so, meaning trying to modify the UI by creating a Toast, will throw a RuntimeException which will look
like this:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
The simplest way of handling this exception is just by using runOnUiThread: syntax is shown below.
runOnUiThread(new Runnable() {
@Override
GoalKicker.com – Android™ Notes for Professionals 524
public void run() {
// Your code here
}
});
Section 78.4: Show Toast Message Above Soft Keyboard
By default, Android will display Toast messages at the bottom of the screen even if the keyboard is showing. This
will show a Toast message just above the keyboard.
public void showMessage(final String message, final int length) {
View root = findViewById(android.R.id.content);
Toast toast = Toast.makeText(this, message, length);
int yOffset = Math.max(0, root.getHeight() - toast.getYOffset());
toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, yOffset);
toast.show();
}
Section 78.5: Thread safe way of displaying Toast
(Application Wide)
public class MainApplication extends Application {
private static Context context; //application context
private Handler mainThreadHandler;
private Toast toast;
public Handler getMainThreadHandler() {
if (mainThreadHandler == null) {
mainThreadHandler = new Handler(Looper.getMainLooper());
}
return mainThreadHandler;
}
@Override public void onCreate() {
super.onCreate();
context = this;
}
public static MainApplication getApp(){
return (MainApplication) context;
}
/**
* Thread safe way of displaying toast.
* @param message
* @param duration
*/
public void showToast(final String message, final int duration) {
getMainThreadHandler().post(new Runnable() {
@Override
public void run() {
if (!TextUtils.isEmpty(message)) {
if (toast != null) {
toast.cancel(); //dismiss current toast if visible
toast.setText(message);
} else {
toast = Toast.makeText(App.this, message, duration);
GoalKicker.com – Android™ Notes for Professionals 525
}
toast.show();
}
}
});
}
Remember to add MainApplication in manifest.
Now call it from any thread to display a toast message.
MainApplication.getApp().showToast("Some message", Toast.LENGTH_LONG);
Section 78.6: Thread safe way of displaying a Toast Message
(For AsyncTask)
If you don't want to extend Application and keep your toast messages thread safe, make sure you show them in the
post execute section of your AsyncTasks.
public class MyAsyncTask extends AsyncTask <Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
// Do your background work here
}
@Override
protected void onPostExecute(Void aVoid) {
// Show toast messages here
Toast.makeText(context, "Ding! Your Toast is ready.", Toast.LENGTH_SHORT).show();
}
}
GoalKicker.com – Android™ Notes for Professionals 526
Chapter 79: Create Singleton Class for
Toast Message
Parameter details
context Relevant context which needs to display your toast message. If you use this in the activity pass "this"
keyword or If you use in fragement pass as "getActivity()".
view Create a custom view and pass that view object to this.
gravity Pass the gravity position of the toaster. All the position has added under the Gravity class as the static
variables . The Most common positions are Gravity.TOP, Gravity.BOTTOM, Gravity.LEFT, Gravity.RIGHT.
xOffset Horizontal offset of the toast message.
yOffset Vertical offset of the toast message.
duration Duration of the toast show. We can set either Toast.LENGTH_SHORT or Toast.LENGTH_LONG
Toast messages are the most simple way of providing feedback to the user. By default, Android provide gray color
message toast where we can set the message and the duration of the message. If we need to create more
customizable and reusable toast message, we can implement it by ourselves with the use of a custom layout. More
importantly when we are implementing it, the use of Singelton design pattern will make it easy for maintaining and
development of the custom toast message class.
Section 79.1: Create own singleton class for toast massages
Here is how to create your own singleton class for toast messages, If your application need to show success,
warning and the danger messages for different use cases you can use this class after you have modified it to your
own specifications.
public class ToastGenerate {
private static ToastGenerate ourInstance;
public ToastGenerate (Context context) {
this.context = context;
}
public static ToastGenerate getInstance(Context context) {
if (ourInstance == null)
ourInstance = new ToastGenerate(context);
return ourInstance;
}
//pass message and message type to this method
public void createToastMessage(String message,int type){
//inflate the custom layout
LayoutInflater layoutInflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout toastLayout = (LinearLayout)
layoutInflater.inflate(R.layout.layout_custome_toast,null);
TextView toastShowMessage = (TextView)
toastLayout.findViewById(R.id.textCustomToastTopic);
switch (type){
case 0:
//if the message type is 0 fail toaster method will call
createFailToast(toastLayout,toastShowMessage,message);
break;
case 1:
GoalKicker.com – Android™ Notes for Professionals 527
//if the message type is 1 success toaster method will call
createSuccessToast(toastLayout,toastShowMessage,message);
break;
case 2:
createWarningToast( toastLayout, toastShowMessage, message);
//if the message type is 2 warning toaster method will call
break;
default:
createFailToast(toastLayout,toastShowMessage,message);
}
}
//Failure toast message method
private final void createFailToast(LinearLayout toastLayout,TextView toastMessage,String
message){
toastLayout.setBackgroundColor(context.getResources().getColor(R.color.button_alert_normal));
toastMessage.setText(message);
toastMessage.setTextColor(context.getResources().getColor(R.color.white));
showToast(context,toastLayout);
}
//warning toast message method
private final void createWarningToast( LinearLayout toastLayout, TextView toastMessage,
String message) {
toastLayout.setBackgroundColor(context.getResources().getColor(R.color.warning_toast));
toastMessage.setText(message);
toastMessage.setTextColor(context.getResources().getColor(R.color.white));
showToast(context, toastLayout);
}
//success toast message method
private final void createSuccessToast(LinearLayout toastLayout,TextView toastMessage,String
message){
toastLayout.setBackgroundColor(context.getResources().getColor(R.color.success_toast));
toastMessage.setText(message);
toastMessage.setTextColor(context.getResources().getColor(R.color.white));
showToast(context,toastLayout);
}
private void showToast(View view){
Toast toast = new Toast(context);
toast.setGravity(Gravity.TOP,0,0); // show message in the top of the device
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(view);
toast.show();
}
}
GoalKicker.com – Android™ Notes for Professionals 528
Chapter 80: Interfaces
Section 80.1: Custom Listener
Define interface
//In this interface, you can define messages, which will be send to owner.
public interface MyCustomListener {
//In this case we have two messages,
//the first that is sent when the process is successful.
void onSuccess(List<Bitmap> bitmapList);
//And The second message, when the process will fail.
void onFailure(String error);
}
Create listener
In the next step we need to define an instance variable in the object that will send callback via MyCustomListener.
And add setter for our listener.
public class SampleClassB {
private MyCustomListener listener;
public void setMyCustomListener(MyCustomListener listener) {
this.listener = listener;
}
}
Implement listener
Now, in other class, we can create instance of SampleClassB.
public class SomeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SampleClassB sampleClass = new SampleClassB();
}
}
next we can set our listener, to sampleClass, in two ways:
by implements MyCustomListener in our class:
public class SomeActivity extends Activity implements MyCustomListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
SampleClassB sampleClass = new SampleClassB();
sampleClass.setMyCustomListener(this);
}
@Override
public void onSuccess(List<Bitmap> bitmapList) {
}
@Override
public void onFailure(String error) {
}
GoalKicker.com – Android™ Notes for Professionals 529
}
or just instantiate an anonymous inner class:
public class SomeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SampleClassB sampleClass = new SampleClassB();
sampleClass.setMyCustomListener(new MyCustomListener() {
@Override
public void onSuccess(List<Bitmap> bitmapList) {
}
@Override
public void onFailure(String error) {
}
});
}
}
Trigger listener
public class SampleClassB {
private MyCustomListener listener;
public void setMyCustomListener(MyCustomListener listener) {
this.listener = listener;
}
public void doSomething() {
fetchImages();
}
private void fetchImages() {
AsyncImagefetch imageFetch = new AsyncImageFetch();
imageFetch.start(new Response<Bitmap>() {
@Override
public void onDone(List<Bitmap> bitmapList, Exception e) {
//do some stuff if needed
//check if listener is set or not.
if(listener == null)
return;
//Fire proper event. bitmapList or error message will be sent to
//class which set listener.
if(e == null)
listener.onSuccess(bitmapList);
else
listener.onFailure(e.getMessage());
}
});
}
}
Section 80.2: Basic Listener
The "listener" or "observer" pattern is the most common strategy for creating asynchronous callbacks in Android
GoalKicker.com – Android™ Notes for Professionals 530
development.
public class MyCustomObject {
//1 - Define the interface
public interface MyCustomObjectListener {
public void onAction(String action);
}
//2 - Declare your listener object
private MyCustomObjectListener listener;
// and initialize it in the costructor
public MyCustomObject() {
this.listener = null;
}
//3 - Create your listener setter
public void setCustomObjectListener(MyCustomObjectListener listener) {
this.listener = listener;
}
// 4 - Trigger listener event
public void makeSomething(){
if (this.listener != null){
listener.onAction("hello!");
}
}
Now on your Activity:
public class MyActivity extends Activity {
public final String TAG = "MyActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
MyCustomObject mObj = new MyCustomObject();
//5 - Implement listener callback
mObj.setCustomObjectListener(new MyCustomObjectListener() {
@Override
public void onAction(String action) {
Log.d(TAG, "Value: "+action);
}
});
}
}
GoalKicker.com – Android™ Notes for Professionals 531
Chapter 81: Animators
Section 81.1: TransitionDrawable animation
This example displays a transaction for an image view with only two images.(can use more images as well one after
the other for the first and second layer positions after each transaction as a loop)
add a image array to res/values/arrays.xml
<resources>
<array
name="splash_images">
<item>@drawable/spash_imge_first</item>
<item>@drawable/spash_img_second</item>
</array>
</resources>
private Drawable[] backgroundsDrawableArrayForTransition;
private TransitionDrawable transitionDrawable;
private void backgroundAnimTransAction() {
// set res image array
Resources resources = getResources();
TypedArray icons = resources.obtainTypedArray(R.array.splash_images);
@SuppressWarnings("ResourceType")
Drawable drawable = icons.getDrawable(0); // ending image
@SuppressWarnings("ResourceType")
Drawable drawableTwo = icons.getDrawable(1); // starting image
backgroundsDrawableArrayForTransition = new Drawable[2];
backgroundsDrawableArrayForTransition[0] = drawable;
backgroundsDrawableArrayForTransition[1] = drawableTwo;
transitionDrawable = new TransitionDrawable(backgroundsDrawableArrayForTransition);
// your image view here - backgroundImageView
backgroundImageView.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(4000);
transitionDrawable.setCrossFadeEnabled(false); // call public methods
}
Section 81.2: Fade in/out animation
In order to get a view to slowly fade in or out of view, use an ObjectAnimator. As seen in the code below, set a
duration using .setDuration(millis) where the millis parameter is the duration (in milliseconds) of the
animation. In the below code, the views will fade in / out over 500 milliseconds, or 1/2 second. To start the
ObjectAnimator's animation, call .start(). Once the animation is complete, onAnimationEnd(Animator
animation) is called. Here is a good place to set your view's visibility to View.GONE or View.VISIBLE.
GoalKicker.com – Android™ Notes for Professionals 532
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
void fadeOutAnimation(View viewToFadeOut) {
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(viewToFadeOut, "alpha", 1f, 0f);
fadeOut.setDuration(500);
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// We wanna set the view to GONE, after it's fade out. so it actually disappear from the
layout & don't take up space.
viewToFadeOut.setVisibility(View.GONE);
}
});
fadeOut.start();
}
void fadeInAnimation(View viewToFadeIn) {
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(viewToFadeIn, "alpha", 0f, 1f);
fadeIn.setDuration(500);
fadeIn.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStar(Animator animation) {
// We wanna set the view to VISIBLE, but with alpha 0. So it appear invisible in the
layout.
viewToFadeIn.setVisibility(View.VISIBLE);
viewToFadeIn.setAlpha(0);
}
});
fadeIn.start();
}
Section 81.3: ValueAnimator
ValueAnimator introduces a simple way to animate a value (of a particular type, e.g. int, float, etc.).
The usual way of using it is:
1. Create a ValueAnimator that will animate a value from min to max
2. Add an UpdateListener in which you will use the calculated animated value (which you can obtain with
getAnimatedValue())
There are two ways you can create the ValueAnimator:
(the example code animates a float from 20f to 40f in 250ms)
1. From xml (put it in the /res/animator/):
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="250"
android:valueFrom="20"
android:valueTo="40"
android:valueType="floatType"/>
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context,
GoalKicker.com – Android™ Notes for Professionals 533
R.animator.example_animator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator anim) {
// ... use the anim.getAnimatedValue()
}
});
// set all the other animation-related stuff you want (interpolator etc.)
animator.start();
2. From the code:
ValueAnimator animator = ValueAnimator.ofFloat(20f, 40f);
animator.setDuration(250);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator anim) {
// use the anim.getAnimatedValue()
}
});
// set all the other animation-related stuff you want (interpolator etc.)
animator.start();
Section 81.4: Expand and Collapse animation of View
public class ViewAnimationUtils {
public static void expand(final View v) {
v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
final int targtetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? LayoutParams.WRAP_CONTENT
: (int)(targtetHeight * interpolatedTime);
v.requestLayout();
}
@Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration((int)(targtetHeight /
v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public static void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();
Animation a = new Animation()
{
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
GoalKicker.com – Android™ Notes for Professionals 534
if(interpolatedTime == 1){
v.setVisibility(View.GONE);
}else{
v.getLayoutParams().height = initialHeight - (int)(initialHeight *
interpolatedTime);
v.requestLayout();
}
}
@Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration((int)(initialHeight /
v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
}
Section 81.5: ObjectAnimator
ObjectAnimator is a subclass of ValueAnimator with the added ability to set the calculated value to the property of
a target View.
Just like in the ValueAnimator, there are two ways you can create the ObjectAnimator:
(the example code animates an alpha of a View from 0.4f to 0.2f in 250ms)
1. From xml (put it in the /res/animator)
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="250"
android:propertyName="alpha"
android:valueFrom="0.4"
android:valueTo="0.2"
android:valueType="floatType"/>
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(context,
R.animator.example_animator);
animator.setTarget(exampleView);
// set all the animation-related stuff you want (interpolator etc.)
animator.start();
2. From code:
ObjectAnimator animator = ObjectAnimator.ofFloat(exampleView, View.ALPHA, 0.4f, 0.2f);
animator.setDuration(250);
// set all the animation-related stuff you want (interpolator etc.)
animator.start();
Section 81.6: ViewPropertyAnimator
ViewPropertyAnimator is a simplified and optimized way to animate properties of a View.
Every single View has a ViewPropertyAnimator object available through the animate() method. You can use that to
animate multiple properties at once with a simple call. Every single method of a ViewPropertyAnimator specifies
the target value of a specific parameter that the ViewPropertyAnimator should animate to.
GoalKicker.com – Android™ Notes for Professionals 535
View exampleView = ...;
exampleView.animate()
.alpha(0.6f)
.translationY(200)
.translationXBy(10)
.scaleX(1.5f)
.setDuration(250)
.setInterpolator(new FastOutLinearInInterpolator());
Note: Calling start() on a ViewPropertyAnimator object is NOT mandatory. If you don't do that you're just letting
the platform to handle the starting of the animation in the appropriate time (next animation handling pass). If you
actually do that (call start()) you're making sure the animation is started immediately.
Section 81.7: Shake animation of an ImageView
Under res folder, create a new folder called "anim" to store your animation resources and put this on that folder.
shakeanimation.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="100"
android:fromDegrees="-15"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toDegrees="15" />
Create a blank activity called Landing
activity_landing.xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgBell"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/ic_notifications_white_48dp"/>
</RelativeLayout>
And the method for animate the imageview on Landing.java
Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext=this;
setContentView(R.layout.activity_landing);
AnimateBell();
GoalKicker.com – Android™ Notes for Professionals 536
}
public void AnimateBell() {
Animation shake = AnimationUtils.loadAnimation(mContext, R.anim.shakeanimation);
ImageView imgBell= (ImageView) findViewById(R.id.imgBell);
imgBell.setImageResource(R.mipmap.ic_notifications_active_white_48dp);
imgBell.setAnimation(shake);
}
GoalKicker.com – Android™ Notes for Professionals 537
Chapter 82: Location
Android Location APIs are used in a wide variety of apps for different purposes such as finding user location,
notifying when a user has left a general area (Geofencing), and help interpret user activity (walking, running, driving,
etc).
However, Android Location APIs are not the only means of acquiring user location. The following will give examples
of how to use Android's LocationManager and other common location libraries.
Section 82.1: Fused location API
Example Using Activity w/ LocationRequest
/*
* This example is useful if you only want to receive updates in this
* activity only, and have no use for location anywhere else.
*/
public class LocationActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mLocationRequest = new LocationRequest()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) //GPS quality location points
.setInterval(2000) //At least once every 2 seconds
.setFastestInterval(1000); //At most once a second
}
@Override
protected void onStart(){
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onResume(){
super.onResume();
//Permission check for Android 6.0+
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
if(mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest, this);
}
}
}
GoalKicker.com – Android™ Notes for Professionals 538
@Override
protected void onPause(){
super.onPause();
//Permission check for Android 6.0+
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
if(mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
}
}
@Override
protected void onStop(){
super.onStop();
mGoogleApiClient.disconnect();
}
@Override
public void onConnected(@Nullable Bundle bundle) {
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest, this);
}
}
@Override
public void onConnectionSuspended(int i) {
mGoogleApiClient.connect();
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
@Override
public void onLocationChanged(Location location) {
//Handle your location update code here
}
}
Example Using Service w/ PendingIntent and BroadcastReceiver
ExampleActivity
Recommended reading: LocalBroadcastManager
/*
* This example is useful if you have many different classes that should be
* receiving location updates, but want more granular control over which ones
* listen to the updates.
*
* For example, this activity will stop getting updates when it is not visible, but a database
* class with a registered local receiver will continue to receive updates, until "stopUpdates()" is
called here.
*
*/
public class ExampleActivity extends AppCompatActivity {
private InternalLocationReceiver mInternalLocationReceiver;
GoalKicker.com – Android™ Notes for Professionals 539
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//Create internal receiver object in this method only.
mInternalLocationReceiver = new InternalLocationReceiver(this);
}
@Override
protected void onResume(){
super.onResume();
//Register to receive updates in activity only when activity is visible
LocalBroadcastManager.getInstance(this).registerReceiver(mInternalLocationReceiver, new
IntentFilter("googleLocation"));
}
@Override
protected void onPause(){
super.onPause();
//Unregister to stop receiving updates in activity when it is not visible.
//NOTE: You will still receive updates even if this activity is killed.
LocalBroadcastManager.getInstance(this).unregisterReceiver(mInternalLocationReceiver);
}
//Helper method to get updates
private void requestUpdates(){
startService(new Intent(this, LocationService.class).putExtra("request", true));
}
//Helper method to stop updates
private void stopUpdates(){
startService(new Intent(this, LocationService.class).putExtra("remove", true));
}
/*
* Internal receiver used to get location updates for this activity.
*
* This receiver and any receiver registered with LocalBroadcastManager does
* not need to be registered in the Manifest.
*
*/
private static class InternalLocationReceiver extends BroadcastReceiver{
private ExampleActivity mActivity;
InternalLocationReceiver(ExampleActivity activity){
mActivity = activity;
}
@Override
public void onReceive(Context context, Intent intent) {
final ExampleActivity activity = mActivity;
if(activity != null) {
LocationResult result = intent.getParcelableExtra("result");
//Handle location update here
}
}
}
}
GoalKicker.com – Android™ Notes for Professionals 540
LocationService
NOTE: Don't forget to register this service in the Manifest!
public class LocationService extends Service implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
@Override
public void onCreate(){
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mLocationRequest = new LocationRequest()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) //GPS quality location points
.setInterval(2000) //At least once every 2 seconds
.setFastestInterval(1000); //At most once a second
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
super.onStartCommand(intent, flags, startId);
//Permission check for Android 6.0+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
if (intent.getBooleanExtra("request", false)) {
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest, getPendingIntent());
} else {
mGoogleApiClient.connect();
}
}
else if(intent.getBooleanExtra("remove", false)){
stopSelf();
}
}
return START_STICKY;
}
@Override
public void onDestroy(){
super.onDestroy();
if(mGoogleApiClient.isConnected()){
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,
getPendingIntent());
mGoogleApiClient.disconnect();
}
}
private PendingIntent getPendingIntent(){
//Example for IntentService
//return PendingIntent.getService(this, 0, new Intent(this,
**YOUR_INTENT_SERVICE_CLASS_HERE**), PendingIntent.FLAG_UPDATE_CURRENT);
GoalKicker.com – Android™ Notes for Professionals 541
//Example for BroadcastReceiver
return PendingIntent.getBroadcast(this, 0, new Intent(this, LocationReceiver.class),
PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void onConnected(@Nullable Bundle bundle) {
//Permission check for Android 6.0+
if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest, getPendingIntent());
}
}
@Override
public void onConnectionSuspended(int i) {
mGoogleApiClient.connect();
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
LocationReceiver
NOTE: Don't forget to register this receiver in the Manifest!
public class LocationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(LocationResult.hasResult(intent)){
LocationResult locationResult = LocationResult.extractResult(intent);
LocalBroadcastManager.getInstance(context).sendBroadcast(new
Intent("googleLocation").putExtra("result", locationResult));
}
}
}
Section 82.2: Get Address From Location using Geocoder
After you got the Location object from FusedAPI, you can easily acquire Address information from that object.
private Address getCountryInfo(Location location) {
Address address = null;
Geocoder geocoder = new Geocoder(getActivity(), Locale.getDefault());
String errorMessage;
List<Address> addresses = null;
try {
addresses = geocoder.getFromLocation(
GoalKicker.com – Android™ Notes for Professionals 542
location.getLatitude(),
location.getLongitude(),
// In this sample, get just a single address.
1);
} catch (IOException ioException) {
// Catch network or other I/O problems.
errorMessage = "IOException>>" + ioException.getMessage();
} catch (IllegalArgumentException illegalArgumentException) {
// Catch invalid latitude or longitude values.
errorMessage = "IllegalArgumentException>>" + illegalArgumentException.getMessage();
}
if (addresses != null && !addresses.isEmpty()) {
address = addresses.get(0);
}
return country;
}
Section 82.3: Requesting location updates using
LocationManager
As always, you need to make sure you have the required permissions.
public class MainActivity extends AppCompatActivity implements LocationListener{
private LocationManager mLocationManager = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
}
@Override
protected void onResume() {
super.onResume();
try {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
}
catch(SecurityException e){
// The app doesn't have the correct permissions
}
}
@Override
protected void onPause() {
try{
mLocationManager.removeUpdates(this);
}
catch (SecurityException e){
// The app doesn't have the correct permissions
}
super.onPause();
}
GoalKicker.com – Android™ Notes for Professionals 543
@Override
public void onLocationChanged(Location location) {
// We received a location update!
Log.i("onLocationChanged", location.toString());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
Section 82.4: Requesting location updates on a separate
thread using LocationManager
As always, you need to make sure you have the required permissions.
public class MainActivity extends AppCompatActivity implements LocationListener{
private LocationManager mLocationManager = null;
HandlerThread mLocationHandlerThread = null;
Looper mLocationHandlerLooper = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mLocationHandlerThread = new HandlerThread("locationHandlerThread");
}
@Override
protected void onResume() {
super.onResume();
mLocationHandlerThread.start();
mLocationHandlerLooper = mLocationHandlerThread.getLooper();
try {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this,
mLocationHandlerLooper);
}
catch(SecurityException e){
// The app doesn't have the correct permissions
}
}
GoalKicker.com – Android™ Notes for Professionals 544
@Override
protected void onPause() {
try{
mLocationManager.removeUpdates(this);
}
catch (SecurityException e){
// The app doesn't have the correct permissions
}
mLocationHandlerLooper = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
mLocationHandlerThread.quitSafely();
else
mLocationHandlerThread.quit();
mLocationHandlerThread = null;
super.onPause();
}
@Override
public void onLocationChanged(Location location) {
// We received a location update on a separate thread!
Log.i("onLocationChanged", location.toString());
// You can verify which thread you're on by something like this:
// Log.d("Which thread?", Thread.currentThread() == Looper.getMainLooper().getThread() ? "UI
Thread" : "New thread");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
Section 82.5: Getting location updates in a BroadcastReceiver
First create a BroadcastReceiver class to handle the incoming Location updates:
public class LocationReceiver extends BroadcastReceiver implements Constants {
@Override
public void onReceive(Context context, Intent intent) {
if (LocationResult.hasResult(intent)) {
GoalKicker.com – Android™ Notes for Professionals 545
LocationResult locationResult = LocationResult.extractResult(intent);
Location location = locationResult.getLastLocation();
if (location != null) {
// Do something with your location
} else {
Log.d(LocationReceiver.class.getSimpleName(), "*** location object is null ***");
}
}
}
}
Then when you connect to the GoogleApiClient in the onConnected callback:
@Override
public void onConnected(Bundle connectionHint) {
Intent backgroundIntent = new Intent(this, LocationReceiver.class);
mBackgroundPendingIntent = backgroundPendingIntent.getBroadcast(getApplicationContext(),
LOCATION_REUEST_CODE, backgroundIntent, PendingIntent.FLAG_CANCEL_CURRENT);
mFusedLocationProviderApi.requestLocationUpdates(mLocationClient, mLocationRequest,
backgroundPendingIntent);
}
Don't forget to remove the location update intent in the appropriate lifecycle callback:
@Override
public void onDestroy() {
if (servicesAvailable && mLocationClient != null) {
if (mLocationClient.isConnected()) {
fusedLocationProviderApi.removeLocationUpdates(mLocationClient,
backgroundPendingIntent);
// Destroy the current location client
mLocationClient = null;
} else {
mLocationClient.unregisterConnectionCallbacks(this);
mLocationClient = null;
}
}
super.onDestroy();
}
Section 82.6: Register geofence
I have created GeoFenceObserversationService singleton class.
GeoFenceObserversationService.java:
public class GeoFenceObserversationService extends Service implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
ResultCallback<Status> {
protected static final String TAG = "GeoFenceObserversationService";
protected GoogleApiClient mGoogleApiClient;
protected ArrayList<Geofence> mGeofenceList;
private boolean mGeofencesAdded;
private SharedPreferences mSharedPreferences;
private static GeoFenceObserversationService mInstant;
public static GeoFenceObserversationService getInstant(){
return mInstant;
}
GoalKicker.com – Android™ Notes for Professionals 546
@Override
public void onCreate() {
super.onCreate();
mInstant = this;
mGeofenceList = new ArrayList<Geofence>();
mSharedPreferences = getSharedPreferences(AppConstants.SHARED_PREFERENCES_NAME,
MODE_PRIVATE);
mGeofencesAdded = mSharedPreferences.getBoolean(AppConstants.GEOFENCES_ADDED_KEY, false);
buildGoogleApiClient();
}
@Override
public void onDestroy() {
mGoogleApiClient.disconnect();
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
protected void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
@Override
public void onConnected(Bundle connectionHint) {
}
@Override
public void onConnectionFailed(ConnectionResult result) {
}
@Override
public void onConnectionSuspended(int cause) {
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
public void addGeofences() {
GoalKicker.com – Android™ Notes for Professionals 547
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
populateGeofenceList();
if(!mGeofenceList.isEmpty()){
try {
LocationServices.GeofencingApi.addGeofences(mGoogleApiClient,
getGeofencingRequest(), getGeofencePendingIntent()).setResultCallback(this);
} catch (SecurityException securityException) {
securityException.printStackTrace();
}
}
}
public void removeGeofences() {
if (!mGoogleApiClient.isConnected()) {
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
}
try {
LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient,getGeofencePendingIntent()).setResu
ltCallback(this);
} catch (SecurityException securityException) {
securityException.printStackTrace();
}
}
public void onResult(Status status) {
if (status.isSuccess()) {
mGeofencesAdded = !mGeofencesAdded;
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(AppConstants.GEOFENCES_ADDED_KEY, mGeofencesAdded);
editor.apply();
} else {
String errorMessage = AppConstants.getErrorString(this,status.getStatusCode());
Log.i("Geofence", errorMessage);
}
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void populateGeofenceList() {
mGeofenceList.clear();
List<GeoFencingResponce> geoFenceList = getGeofencesList;
if(geoFenceList!=null&&!geoFenceList.isEmpty()){
for (GeoFencingResponce obj : geoFenceList){
mGeofenceList.add(obj.getGeofence());
Log.i(TAG,"Registered Geofences : " + obj.Id+"-"+obj.Name+"-"+obj.Lattitude+"-
"+obj.Longitude);
}
}
}
}
GoalKicker.com – Android™ Notes for Professionals 548
AppConstant:
public static final String SHARED_PREFERENCES_NAME = PACKAGE_NAME + ".SHARED_PREFERENCES_NAME";
public static final String GEOFENCES_ADDED_KEY = PACKAGE_NAME + ".GEOFENCES_ADDED_KEY";
public static final String DETECTED_GEOFENCES = "detected_geofences";
public static final String DETECTED_BEACONS = "detected_beacons";
public static String getErrorString(Context context, int errorCode) {
Resources mResources = context.getResources();
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return mResources.getString(R.string.geofence_not_available);
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return mResources.getString(R.string.geofence_too_many_geofences);
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return mResources.getString(R.string.geofence_too_many_pending_intents);
default:
return mResources.getString(R.string.unknown_geofence_error);
}
}
Where I started Service ? From Application class
startService(new Intent(getApplicationContext(),GeoFenceObserversationService.class));
How I registered Geofences ?
GeoFenceObserversationService.getInstant().addGeofences();
GoalKicker.com – Android™ Notes for Professionals 549
Chapter 83: Theme, Style, Attribute
Section 83.1: Define primary, primary dark, and accent colors
You can customize your theme’s color palette.
Using framework APIs
Version ≥ 5.0
<style name="AppTheme" parent="Theme.Material">
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<item name="android:colorAccent">@color/accent</item>
</style>
Using the Appcompat support library (and AppCompatActivity)
Version ≥ 2.1.x
<style name="AppTheme" parent="Theme.AppCompat">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
</style>
Section 83.2: Multiple Themes in one App
Using more than one theme in your Android application, you can add custom colors to every theme, to be like this:
First, we have to add our themes to style.xml like this:
<style name="OneTheme" parent="Theme.AppCompat.Light.DarkActionBar">
</style>
<!-- -->
<style name="TwoTheme" parent="Theme.AppCompat.Light.DarkActionBar" >
GoalKicker.com – Android™ Notes for Professionals 550
</style>
......
Above you can see OneTheme and TwoTheme.
Now, go to your AndroidManifest.xml and add this line: android:theme="@style/OneTheme" to your application
tag, this will make OneTheme the default theme:
<application
android:theme="@style/OneTheme"
...>
Create new xml file named attrs.xml and add this code :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="custom_red" format="color" />
<attr name="custom_blue" format="color" />
<attr name="custom_green" format="color" />
</resources>
<!-- add all colors you need (just color's name) -->
Go back to style.xml and add these colors with its values for each theme :
<style name="OneTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="custom_red">#8b030c</item>
<item name="custom_blue">#0f1b8b</item>
<item name="custom_green">#1c7806</item>
</style>
<style name="TwoTheme" parent="Theme.AppCompat.Light.DarkActionBar" >
<item name="custom_red">#ff606b</item>
<item name="custom_blue">#99cfff</item>
<item name="custom_green">#62e642</item>
</style>
Now you have custom colors for each theme, let's add these color to our views.
Add custom_blue color to the TextView by using "?attr/" :
Go to your imageView and add this color :
<TextView>
android:id="@+id/txte_view"
android:textColor="?attr/custom_blue" />
Mow we can change the theme just by single line setTheme(R.style.TwoTheme); this line must be before
setContentView() method in onCreate() method, like this Activity.java :
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.TwoTheme);
setContentView(R.layout.main_activity);
....
}
GoalKicker.com – Android™ Notes for Professionals 551
change theme for all activities at once
If we want to change the theme for all activities, we have to create new class named MyActivity extends
AppCompatActivity class (or Activity class) and add line setTheme(R.style.TwoTheme); to onCreate() method:
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (new MySettings(this).isDarkTheme())
setTheme(R.style.TwoTheme);
}
}
Finally, go to all your activities add make all of them extend the MyActivity base class:
public class MainActivity extends MyActivity {
....
}
In order to change the theme, just go to MyActivity and change R.style.TwoTheme to your theme
(R.style.OneTheme , R.style.ThreeTheme ....).
Section 83.3: Navigation Bar Color (API 21+)
Version ≥ 5.0
This attribute is used to change the navigation bar (one, that contain Back, Home Recent button). Usually it is black,
however it's color can be changed.
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:navigationBarColor">@color/my_color</item>
</style>
Section 83.4: Use Custom Theme Per Activity
In themes.xml:
<style name="MyActivityTheme" parent="Theme.AppCompat">
<!-- Theme attributes here -->
</style>
In AndroidManifest.xml:
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat">
<activity
android:name=".MyActivity"
android:theme="@style/MyActivityTheme" />
</application>
GoalKicker.com – Android™ Notes for Professionals 552
Section 83.5: Light Status Bar (API 23+)
This attribute can change the background of the Status Bar icons (at the top of the screen) to white.
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:windowLightStatusBar">true</item>
</style>
Section 83.6: Use Custom Theme Globally
In themes.xml:
<style name="AppTheme" parent="Theme.AppCompat">
<!-- Theme attributes here -->
</style>
In AndroidManifest.xml:
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<!-- Activity declarations here -->
</application>
Section 83.7: Overscroll Color (API 21+)
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:colorEdgeEffect">@color/my_color</item>
</style>
Section 83.8: Ripple Color (API 21+)
Version ≥ 5.0
The ripple animation is shown when user presses clickable views.
You can use the same ripple color used by your app assigning the ?android:colorControlHighlight in your views.
You can customize this color by changing the android:colorControlHighlight attribute in your theme:
This effect color can be changed:
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:colorControlHighlight">@color/my_color</item>
</style>
Or, if you are using a Material Theme:
<style name="AppTheme" parent="android:Theme.Material.Light">
<item name="android:colorControlHighlight">@color/your_custom_color</item>
</style>
GoalKicker.com – Android™ Notes for Professionals 553
Section 83.9: Translucent Navigation and Status Bars (API 19+)
The navigation bar (at the bottom of the screen) can be transparent. Here is the way to achieve it.
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:windowTranslucentNavigation">true</item>
</style>
The Status Bar (top of the screen) can be made transparent, by applying this attribute to the style:
<style name="AppTheme" parent="Theme.AppCompat">
<item name="android:windowTranslucentStatus">true</item>
</style>
Section 83.10: Theme inheritance
When defining themes, one usually uses the theme provided by the system, and then changes modifies the look to
fit his own application. For example, this is how the Theme.AppCompat theme is inherited:
<style name="AppTheme" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
This theme now has all the properties of the standard Theme.AppCompat theme, except the ones we explicitly
changed.
There is also a shortcut when inheriting, usually used when one inherits from his own theme:
<style name="AppTheme.Red">
<item name="colorAccent">@color/red</item>
</style>
Since it already has AppTheme. in the start of it's name, it automatically inherits it, without needing to define the
parent theme. This is useful when you need to create specific styles for a part (for example, a single Activity) of your
app.
GoalKicker.com – Android™ Notes for Professionals 554
Chapter 84: MediaPlayer
Section 84.1: Basic creation and playing
MediaPlayer class can be used to control playback of audio/video files and streams.
Creation of MediaPlayer object can be of three types:
1. Media from local resource
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.resource);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
2. From local URI (obtained from ContentResolver)
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
3. From external URL
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
Section 84.2: Media Player with Buer progress and play
position
public class SoundActivity extends Activity {
private MediaPlayer mediaPlayer;
ProgressBar progress_bar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tool_sound);
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
progress_bar = (ProgressBar) findViewById(R.id.progress_bar);
btn_play_stop.setEnabled(false);
btn_play_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mediaPlayer.isPlaying()) {
mediaPlayer.pause();
btn_play_stop.setImageResource(R.drawable.ic_pause_black_24dp);
} else {
mediaPlayer.start();
GoalKicker.com – Android™ Notes for Professionals 555
btn_play_stop.setImageResource(R.drawable.ic_play_arrow_black_24px);
}
}
});
mediaPlayer.setDataSource(proxyUrl);
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
observer.stop();
progress_bar.setProgress(mp.getCurrentPosition());
// TODO Auto-generated method stub
mediaPlayer.stop();
mediaPlayer.reset();
}
});
mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
progress_bar.setSecondaryProgress(percent);
}
});
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
btn_play_stop.setEnabled(true);
}
});
observer = new MediaObserver();
mediaPlayer.prepare();
mediaPlayer.start();
new Thread(observer).start();
}
private MediaObserver observer = null;
private class MediaObserver implements Runnable {
private AtomicBoolean stop = new AtomicBoolean(false);
public void stop() {
stop.set(true);
}
@Override
public void run() {
while (!stop.get()) {
progress_bar.setProgress((int)((double)mediaPlayer.getCurrentPosition() /
(double)mediaPlayer.getDuration()*100));
try {
Thread.sleep(200);
} catch (Exception ex) {
Logger.log(ToolSoundActivity.this, ex);
}
}
}
}
@Override
protected void onDestroy() {
GoalKicker.com – Android™ Notes for Professionals 556
super.onDestroy();
mediaPlayer.stop();
}
}
<LinearLayout
android:gravity="bottom"
android:layout_gravity="bottom"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:weightSum="1">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
app:srcCompat="@drawable/ic_play_arrow_black_24px"
android:layout_width="48dp"
android:layout_height="48dp"
android:id="@+id/btn_play_stop" />
<ProgressBar
android:padding="8dp"
android:progress="0"
android:id="@+id/progress_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
</LinearLayout>
Section 84.3: Getting system ringtones
This example demonstrates how to fetch the URI's of system ringtones (RingtoneManager.TYPE_RINGTONE):
private List<Uri> loadLocalRingtonesUris() {
List<Uri> alarms = new ArrayList<>();
try {
RingtoneManager ringtoneMgr = new RingtoneManager(getActivity());
ringtoneMgr.setType(RingtoneManager.TYPE_RINGTONE);
Cursor alarmsCursor = ringtoneMgr.getCursor();
int alarmsCount = alarmsCursor.getCount();
if (alarmsCount == 0 && !alarmsCursor.moveToFirst()) {
alarmsCursor.close();
return null;
}
while (!alarmsCursor.isAfterLast() && alarmsCursor.moveToNext()) {
int currentPosition = alarmsCursor.getPosition();
alarms.add(ringtoneMgr.getRingtoneUri(currentPosition));
}
GoalKicker.com – Android™ Notes for Professionals 557
} catch (Exception ex) {
ex.printStackTrace();
}
return alarms;
}
The list depends on the types of requested ringtones. The possibilities are:
RingtoneManager.TYPE_RINGTONE
RingtoneManager.TYPE_NOTIFICATION
RingtoneManager.TYPE_ALARM
RingtoneManager.TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM
In order to get the Ringtones as android.media.Ringtone every Uri must be resolved by the RingtoneManager:
android.media.Ringtone osRingtone = RingtoneManager.getRingtone(context, uri);
To play the sound, use the method:
public void setDataSource(Context context, Uri uri)
from android.media.MediaPlayer. MediaPlayer must be initialised and prepared according to the State diagram
Section 84.4: Asynchronous prepare
The MediaPlayer$prepare() is a blocking call and will freeze the UI till execution completes. To solve this problem,
MediaPlayer$prepareAsync() can be used.
mMediaPlayer = ... // Initialize it here
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer player) {
// Called when the MediaPlayer is ready to play
mMediaPlayer.start();
}
}); // Set callback for when prepareAsync() finishes
mMediaPlayer.prepareAsync(); // Prepare asynchronously to not block the Main Thread
On synchronous operations, errors would normally be signaled with an exception or an error code, but whenever
you use asynchronous resources, you should make sure your application is notified of errors appropriately. For
MediaPlayer,
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener(){
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
// Then return true if the error has been handled
}
});
Section 84.5: Import audio into androidstudio and play it
This is an example how to get the play an audio file which you already have on your pc/laptop .First create a new
GoalKicker.com – Android™ Notes for Professionals 558
directory under res and name it as raw like this
copy the audio which you want to play into this folder .It may be a .mp3 or .wav file.
Now for example on button click you want to play this sound ,here is how it is done
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aboutapp_activity);
MediaPlayer song=MediaPlayer.create(this, R.raw.song);
Button button=(Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
song.start();
}
});
}
}
This will play the song only once when the button is clicked,if you want to replay the song on every button click
write code like this
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aboutapp_activity);
MediaPlayer song=MediaPlayer.create(this, R.raw.song);
Button button=(Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
GoalKicker.com – Android™ Notes for Professionals 559
@Override
public void onClick(View view) {
if (song.isPlaying()) {
song.reset();
song= MediaPlayer.create(getApplicationContext(), R.raw.song);
}
song.start();
}
});
}
}
Section 84.6: Getting and setting system volume
Audio stream types
There are different profiles of ringtone streams. Each one of them has it's different volume.
Every example here is written for AudioManager.STREAM_RING stream type. However this is not the only one. The
available stream types are:
STREAM_ALARM
STREAM_DTMF
STREAM_MUSIC
STREAM_NOTIFICATION
STREAM_RING
STREAM_SYSTEM
STREAM_VOICE_CALL
Setting volume
To get the volume of specific profile, call:
AudioManager audio = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
This value is very little useful, when the maximum value for the stream is not known:
AudioManager audio = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
int streamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
The ratio of those two value will give a relative volume (0 < volume < 1):
float volume = ((float) currentVolume) / streamMaxVolume
Adjusting volume by one step
To make the volume for the stream higher by one step, call:
AudioManager audio = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
audio.adjustStreamVolume(AudioManager.STREAM_RING, AudioManager.ADJUST_RAISE, 0);
GoalKicker.com – Android™ Notes for Professionals 560
To make the volume for the stream lower by one step, call:
AudioManager audio = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
audio.adjustStreamVolume(AudioManager.STREAM_RING, AudioManager.ADJUST_LOWER, 0);
Setting MediaPlayer to use specific stream type
There is a helper function from MediaPlayer class to do this.
Just call void setAudioStreamType(int streamtype):
MediaPlayer mMedia = new MediaPlayer();
mMedia.setAudioStreamType(AudioManager.STREAM_RING);
GoalKicker.com – Android™ Notes for Professionals 561
Chapter 85: Android Sound and Media
Section 85.1: How to pick image and video for api >19
Here is a tested code for image and video.It will work for all APIs less than 19 and greater than 19 as well.
Image:
if (Build.VERSION.SDK_INT <= 19) {
Intent i = new Intent();
i.setType("image/*");
i.setAction(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(i, 10);
} else if (Build.VERSION.SDK_INT > 19) {
Intent intent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, 10);
}
Video:
if (Build.VERSION.SDK_INT <= 19) {
Intent i = new Intent();
i.setType("video/*");
i.setAction(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(i, 20);
} else if (Build.VERSION.SDK_INT > 19) {
Intent intent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, 20);
}
.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == 10) {
Uri selectedImageUri = data.getData();
String selectedImagePath = getRealPathFromURI(selectedImageUri);
} else if (requestCode == 20) {
Uri selectedVideoUri = data.getData();
String selectedVideoPath = getRealPathFromURI(selectedVideoUri);
}
public String getRealPathFromURI(Uri uri) {
if (uri == null) {
return null;
}
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = getActivity().getContentResolver().query(uri, projection, null, null,
null);
if (cursor != null) {
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
GoalKicker.com – Android™ Notes for Professionals 562
cursor.moveToFirst();
return cursor.getString(column_index);
}
return uri.getPath();
}
Section 85.2: Play sounds via SoundPool
public class PlaySound extends Activity implements OnTouchListener {
private SoundPool soundPool;
private int soundID;
boolean loaded = false;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View view = findViewById(R.id.textView1);
view.setOnTouchListener(this);
// Set the hardware buttons to control the music
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
// Load the sound
soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId,
int status) {
loaded = true;
}
});
soundID = soundPool.load(this, R.raw.sound1, 1);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// Getting the user sound settings
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
float actualVolume = (float) audioManager
.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxVolume = (float) audioManager
.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = actualVolume / maxVolume;
// Is the sound loaded already?
if (loaded) {
soundPool.play(soundID, volume, volume, 1, 0, 1f);
Log.e("Test", "Played sound");
}
}
return false;
}
}
GoalKicker.com – Android™ Notes for Professionals 563
Chapter 86: MediaSession
Section 86.1: Receiving and handling button events
This example creates a MediaSession object when a Service is started. The MediaSession object is released when
the Service gets destroyed:
public final class MyService extends Service {
private static MediaSession s_mediaSession;
@Override
public void onCreate() {
// Instantiate new MediaSession object.
configureMediaSession();
}
@Override
public void onDestroy() {
if (s_mediaSession != null)
s_mediaSession.release();
}
}
The following method instantiates and configures the MediaSession button callbacks:
private void configureMediaSession {
s_mediaSession = new MediaSession(this, "MyMediaSession");
// Overridden methods in the MediaSession.Callback class.
s_mediaSession.setCallback(new MediaSession.Callback() {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
Log.d(TAG, "onMediaButtonEvent called: " + mediaButtonIntent);
KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
int keyCode = ke.getKeyCode();
Log.d(TAG, "onMediaButtonEvent Received command: " + ke);
}
return super.onMediaButtonEvent(mediaButtonIntent);
}
@Override
public void onSkipToNext() {
Log.d(TAG, "onSkipToNext called (media button pressed)");
Toast.makeText(getApplicationContext(), "onSkipToNext called",
Toast.LENGTH_SHORT).show();
skipToNextPlaylistItem(); // Handle this button press.
super.onSkipToNext();
}
@Override
public void onSkipToPrevious() {
Log.d(TAG, "onSkipToPrevious called (media button pressed)");
Toast.makeText(getApplicationContext(), "onSkipToPrevious called",
Toast.LENGTH_SHORT).show();
skipToPreviousPlaylistItem(); // Handle this button press.
super.onSkipToPrevious();
}
GoalKicker.com – Android™ Notes for Professionals 564
@Override
public void onPause() {
Log.d(TAG, "onPause called (media button pressed)");
Toast.makeText(getApplicationContext(), "onPause called", Toast.LENGTH_SHORT).show();
mpPause(); // Pause the player.
super.onPause();
}
@Override
public void onPlay() {
Log.d(TAG, "onPlay called (media button pressed)");
mpStart(); // Start player/playback.
super.onPlay();
}
@Override
public void onStop() {
Log.d(TAG, "onStop called (media button pressed)");
mpReset(); // Stop and/or reset the player.
super.onStop();
}
});
s_mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
s_mediaSession.setActive(true);
}
The following method sends meta data (stored in a HashMap) to the device using A2DP:
void sendMetaData(@NonNull final HashMap<String, String> hm) {
// Return if Bluetooth A2DP is not in use.
if (!((AudioManager) getSystemService(Context.AUDIO_SERVICE)).isBluetoothA2dpOn()) return;
MediaMetadata metadata = new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_TITLE, hm.get("Title"))
.putString(MediaMetadata.METADATA_KEY_ALBUM, hm.get("Album"))
.putString(MediaMetadata.METADATA_KEY_ARTIST, hm.get("Artist"))
.putString(MediaMetadata.METADATA_KEY_AUTHOR, hm.get("Author"))
.putString(MediaMetadata.METADATA_KEY_COMPOSER, hm.get("Composer"))
.putString(MediaMetadata.METADATA_KEY_WRITER, hm.get("Writer"))
.putString(MediaMetadata.METADATA_KEY_DATE, hm.get("Date"))
.putString(MediaMetadata.METADATA_KEY_GENRE, hm.get("Genre"))
.putLong(MediaMetadata.METADATA_KEY_YEAR, tryParse(hm.get("Year")))
.putLong(MediaMetadata.METADATA_KEY_DURATION, tryParse(hm.get("Raw Duration")))
.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, tryParse(hm.get("Track Number")))
.build();
s_mediaSession.setMetadata(metadata);
}
The following method sets the PlaybackState. It also sets which button actions the MediaSession will respond to:
private void setPlaybackState(@NonNull final int stateValue) {
PlaybackState state = new PlaybackState.Builder()
.setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_SKIP_TO_NEXT
| PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_SKIP_TO_PREVIOUS
| PlaybackState.ACTION_STOP | PlaybackState.ACTION_PLAY_PAUSE)
.setState(stateValue, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0)
.build();
GoalKicker.com – Android™ Notes for Professionals 565
s_mediaSession.setPlaybackState(state);
}
GoalKicker.com – Android™ Notes for Professionals 566
Chapter 87: MediaStore
Section 87.1: Fetch Audio/MP3 files from specific folder of
device or fetch all files
First, add the following permissions to the manifest of your project in order to enable device storage access:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Then, create the file AudioModel.class and put the following model class into it in order to allow getting and setting
list items:
public class AudioModel {
String aPath;
String aName;
String aAlbum;
String aArtist;
public String getaPath() {
return aPath;
}
public void setaPath(String aPath) {
this.aPath = aPath;
}
public String getaName() {
return aName;
}
public void setaName(String aName) {
this.aName = aName;
}
public String getaAlbum() {
return aAlbum;
}
public void setaAlbum(String aAlbum) {
this.aAlbum = aAlbum;
}
public String getaArtist() {
return aArtist;
}
public void setaArtist(String aArtist) {
this.aArtist = aArtist;
}
}
Next, use the following method to read all MP3 files from a folder of your device or to read all files of your device:
public List<AudioModel> getAllAudioFromDevice(final Context context) {
final List<AudioModel> tempAudioList = new ArrayList<>();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = {MediaStore.Audio.AudioColumns.DATA, MediaStore.Audio.AudioColumns.TITLE,
MediaStore.Audio.AudioColumns.ALBUM, MediaStore.Audio.ArtistColumns.ARTIST,};
Cursor c = context.getContentResolver().query(uri, projection, MediaStore.Audio.Media.DATA + "
like ? ", new String[]{"%utm%"}, null);
if (c != null) {
while (c.moveToNext()) {
GoalKicker.com – Android™ Notes for Professionals 567
AudioModel audioModel = new AudioModel();
String path = c.getString(0);
String name = c.getString(1);
String album = c.getString(2);
String artist = c.getString(3);
audioModel.setaName(name);
audioModel.setaAlbum(album);
audioModel.setaArtist(artist);
audioModel.setaPath(path);
Log.e("Name :" + name, " Album :" + album);
Log.e("Path :" + path, " Artist :" + artist);
tempAudioList.add(audioModel);
}
c.close();
}
return tempAudioList;
}
The code above will return a list of all MP3 files with the music's name, path, artist, and album. For more details
please refer to the Media.Store.Audio documentation.
In order to read files of a specific folder, use the following query (you need to replace the folder name):
Cursor c = context.getContentResolver().query(uri,
projection,
MediaStore.Audio.Media.DATA + " like ? ",
new String[]{"%yourFolderName%"}, // Put your device folder / file location here.
null);
If you want to retrieve all files from your device, then use the following query:
Cursor c = context.getContentResolver().query(uri,
projection,
null,
null,
null);
Note: Don't forget to enable storage access permissions.
Now, all you have to do is to call the method above in order to get the MP3 files:
getAllAudioFromDevice(this);
Example with Activity
public class ReadAudioFilesActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_list);
/**
* This will return a list of all MP3 files. Use the list to display data.
*/
getAllAudioFromDevice(this);
}
GoalKicker.com – Android™ Notes for Professionals 568
// Method to read all the audio/MP3 files.
public List<AudioModel> getAllAudioFromDevice(final Context context) {
final List<AudioModel> tempAudioList = new ArrayList<>();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection =
{MediaStore.Audio.AudioColumns.DATA,MediaStore.Audio.AudioColumns.TITLE
,MediaStore.Audio.AudioColumns.ALBUM, MediaStore.Audio.ArtistColumns.ARTIST,};
Cursor c = context.getContentResolver().query(uri, projection, MediaStore.Audio.Media.DATA
+ " like ? ", new String[]{"%utm%"}, null);
if (c != null) {
while (c.moveToNext()) {
// Create a model object.
AudioModel audioModel = new AudioModel();
String path = c.getString(0); // Retrieve path.
String name = c.getString(1); // Retrieve name.
String album = c.getString(2); // Retrieve album name.
String artist = c.getString(3); // Retrieve artist name.
// Set data to the model object.
audioModel.setaName(name);
audioModel.setaAlbum(album);
audioModel.setaArtist(artist);
audioModel.setaPath(path);
Log.e("Name :" + name, " Album :" + album);
Log.e("Path :" + path, " Artist :" + artist);
// Add the model object to the list .
tempAudioList.add(audioModel);
}
c.close();
}
// Return the list.
return tempAudioList;
}
}
GoalKicker.com – Android™ Notes for Professionals 569
Chapter 88: Multidex and the Dex Method
Limit
DEX means Android app's (APK) executable bytecode files in the form of Dalvik Executable (DEX) files, which contain
the compiled code used to run your app.
The Dalvik Executable specification limits the total number of methods that can be referenced within a single DEX
file to 65,536 (64K)—including Android framework methods, library methods, and methods in your own code.
To overcome this limit requires configure your app build process to generate more than one DEX file, known as a
Multidex.
Section 88.1: Enabling Multidex
In order to enable a multidex configuration you need:
to change your Gradle build configuration
to use a MultiDexApplication or enable the MultiDex in your Application class
Gradle configuration
In app/build.gradle add these parts:
android {
compileSdkVersion 24
buildToolsVersion "24.0.1"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 24
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
Enable MultiDex in your Application
Then proceed with one of three options:
Multidex by extending Application
Multidex by extending MultiDexApplication
Multidex by using MultiDexApplication directly
When these configuration settings are added to an app, the Android build tools construct a primary dex
(classes.dex) and supporting (classes2.dex, classes3.dex) as needed.
The build system will then package them into an APK file for distribution.
GoalKicker.com – Android™ Notes for Professionals 570
Section 88.2: Multidex by extending Application
Use this option if your project requires an Application subclass.
Specify this Application subclass using the android:name property in the manifest file inside the application tag.
In the Application subclass, add the attachBaseContext() method override, and in that method call
MultiDex.install():
package com.example;
import android.app.Application;
import android.content.Context;
/**
* Extended application that support multidex
*/
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
Ensure that the Application subclass is specified in the application tag of your AndroidManifest.xml:
<application
android:name="com.example.MyApplication"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
</application>
Section 88.3: Multidex by extending MultiDexApplication
This is very similar to using an Application subclass and overriding the attachBaseContext() method.
However, using this method, you don't need to override attachBaseContext() as this is already done in the
MultiDexApplication superclass.
Extend MultiDexApplication instead of Application:
package com.example;
import android.support.multidex.MultiDexApplication;
import android.content.Context;
/**
* Extended MultiDexApplication
*/
public class MyApplication extends MultiDexApplication {
// No need to override attachBaseContext()
//..........
}
GoalKicker.com – Android™ Notes for Professionals 571
Add this class to your AndroidManifest.xml exactly as if you were extending Application:
<application
android:name="com.example.MyApplication"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
</application>
Section 88.4: Multidex by using MultiDexApplication directly
Use this option if you don't need an Application subclass.
This is the simplest option, but this way you can't provide your own Application subclass. If an Application
subclass is needed, you will have to switch to one of the other options to do so.
For this option, simply specify the fully-qualified class name android.support.multidex.MultiDexApplication for
the android:name property of the application tag in the AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidex.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
Section 88.5: Counting Method References On Every Build
(Dexcount Gradle Plugin)
The dexcount plugin counts methods and class resource count after a successful build.
Add the plugin in the app/build.gradle:
apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral() // or jcenter()
}
dependencies {
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.5'
}
}
Apply the plugin in the app/build.gradle file:
apply plugin: 'com.getkeepsafe.dexcount'
Look for the output data generated by the plugin in:
../app/build/outputs/dexcount
Especially useful is the .html chart in:
GoalKicker.com – Android™ Notes for Professionals 572
../app/build/outputs/dexcount/debugChart/index.html
GoalKicker.com – Android™ Notes for Professionals 573
Chapter 89: Data Synchronization with
Sync Adapter
Section 89.1: Dummy Sync Adapter with Stub Provider
SyncAdapter
/**
* Define a sync adapter for the app.
* <p/>
* <p>This class is instantiated in {@link SyncService}, which also binds SyncAdapter to the system.
* SyncAdapter should only be initialized in SyncService, never anywhere else.
* <p/>
* <p>The system calls onPerformSync() via an RPC call through the IBinder object supplied by
* SyncService.
*/
class SyncAdapter extends AbstractThreadedSyncAdapter {
/**
* Constructor. Obtains handle to content resolver for later use.
*/
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
/**
* Constructor. Obtains handle to content resolver for later use.
*/
public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
super(context, autoInitialize, allowParallelSyncs);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
//Jobs you want to perform in background.
Log.e("" + account.name, "Sync Start");
}
}
Sync Service
/**
* Define a Service that returns an IBinder for the
* sync adapter class, allowing the sync adapter framework to call
* onPerformSync().
*/
public class SyncService extends Service {
// Storage for an instance of the sync adapter
private static SyncAdapter sSyncAdapter = null;
// Object to use as a thread-safe lock
private static final Object sSyncAdapterLock = new Object();
/*
* Instantiate the sync adapter object.
*/
@Override
public void onCreate() {
/*
* Create the sync adapter as a singleton.
* Set the sync adapter as syncable
GoalKicker.com – Android™ Notes for Professionals 574
* Disallow parallel syncs
*/
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
/**
* Return an object that allows the system to invoke
* the sync adapter.
*/
@Override
public IBinder onBind(Intent intent) {
/*
* Get the object that allows external processes
* to call onPerformSync(). The object is created
* in the base class code when the SyncAdapter
* constructors call super()
*/
return sSyncAdapter.getSyncAdapterBinder();
}
}
Authenticator
public class Authenticator extends AbstractAccountAuthenticator {
// Simple constructor
public Authenticator(Context context) {
super(context);
}
// Editing properties is not supported
@Override
public Bundle editProperties(
AccountAuthenticatorResponse r, String s) {
throw new UnsupportedOperationException();
}
// Don't add additional accounts
@Override
public Bundle addAccount(
AccountAuthenticatorResponse r,
String s,
String s2,
String[] strings,
Bundle bundle) throws NetworkErrorException {
return null;
}
// Ignore attempts to confirm credentials
@Override
public Bundle confirmCredentials(
AccountAuthenticatorResponse r,
Account account,
Bundle bundle) throws NetworkErrorException {
return null;
}
// Getting an authentication token is not supported
@Override
public Bundle getAuthToken(
GoalKicker.com – Android™ Notes for Professionals 575
AccountAuthenticatorResponse r,
Account account,
String s,
Bundle bundle) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
// Getting a label for the auth token is not supported
@Override
public String getAuthTokenLabel(String s) {
throw new UnsupportedOperationException();
}
// Updating user credentials is not supported
@Override
public Bundle updateCredentials(
AccountAuthenticatorResponse r,
Account account,
String s, Bundle bundle) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
// Checking features for the account is not supported
@Override
public Bundle hasFeatures(
AccountAuthenticatorResponse r,
Account account, String[] strings) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
}
Authenticator Service
/**
* A bound Service that instantiates the authenticator
* when started.
*/
public class AuthenticatorService extends Service {
// Instance field that stores the authenticator object
private Authenticator mAuthenticator;
@Override
public void onCreate() {
// Create a new authenticator object
mAuthenticator = new Authenticator(this);
}
/*
* When the system binds to this Service to make the RPC call
* return the authenticator's IBinder.
*/
@Override
public IBinder onBind(Intent intent) {
return mAuthenticator.getIBinder();
}
}
AndroidManifest.xml additions
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<service
android:name=".syncAdapter.SyncService"
GoalKicker.com – Android™ Notes for Professionals 576
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
</service>
<service android:name=".authenticator.AuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<provider
android:name=".provider.StubProvider"
android:authorities="com.yourpackage.provider"
android:exported="false"
android:syncable="true" />
res/xml/authenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.yourpackage"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:smallIcon="@mipmap/ic_launcher" />
res/xml/syncadapter.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.yourpackage.android"
android:allowParallelSyncs="false"
android:contentAuthority="com.yourpackage.provider"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="false" />
StubProvider
/*
* Define an implementation of ContentProvider that stubs out
* all methods
*/
public class StubProvider extends ContentProvider {
/*
* Always return true, indicating that the
* provider loaded correctly.
*/
@Override
public boolean onCreate() {
return true;
}
/*
* Return no type for MIME type
*/
@Override
public String getType(Uri uri) {
return null;
GoalKicker.com – Android™ Notes for Professionals 577
}
/*
* query() always returns no results
*
*/
@Override
public Cursor query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
return null;
}
/*
* insert() always returns null (no URI)
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
/*
* delete() always returns "no rows affected" (0)
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
/*
* update() always returns "no rows affected" (0)
*/
public int update(
Uri uri,
ContentValues values,
String selection,
String[] selectionArgs) {
return 0;
}
}
Call this function on successful login to create an account with the logged-in user ID
public Account CreateSyncAccount(Context context, String accountName) {
// Create the account type and default account
Account newAccount = new Account(
accountName, "com.yourpackage");
// Get an instance of the Android account manager
AccountManager accountManager =
(AccountManager) context.getSystemService(
ACCOUNT_SERVICE);
/*
* Add the account and account type, no password or user data
* If successful, return the Account object, otherwise report an error.
*/
if (accountManager.addAccountExplicitly(newAccount, null, null)) {
/*
* If you don't set android:syncable="true" in
* in your <provider> element in the manifest,
* then call context.setIsSyncable(account, AUTHORITY, 1)
GoalKicker.com – Android™ Notes for Professionals 578
* here.
*/
} else {
/*
* The account exists or some other error occurred. Log this, report it,
* or handle it internally.
*/
}
return newAccount;
}
Forcing a Sync
Bundle bundle = new Bundle();
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
ContentResolver.requestSync(null, MyContentProvider.getAuthority(), bundle);
GoalKicker.com – Android™ Notes for Professionals 579
Chapter 90: PorterDu Mode
PorterDuff is described as a way of combining images as if they were "irregular shaped pieces of cardboard"
overlayed on each other, as well as a scheme for blending the overlapping parts
Section 90.1: Creating a PorterDu ColorFilter
PorterDuff.Mode is used to create a PorterDuffColorFilter. A color filter modifies the color of each pixel of a
visual resource.
ColorFilter filter = new PorterDuffColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
The above filter will tint the non-transparent pixels to blue color.
The color filter can be applied to a Drawable:
drawable.setColorFilter(filter);
It can be applied to an ImageView:
imageView.setColorFilter(filter);
Also, it can be applied to a Paint, so that the color that is drawn using that paint, is modified by the filter:
paint.setColorFilter(filter);
Section 90.2: Creating a PorterDu XferMode
An Xfermode (think "transfer" mode) works as a transfer step in drawing pipeline. When an Xfermode is applied to a
Paint, the pixels drawn with the paint are combined with underlying pixels (already drawn) as per the mode:
paint.setColor(Color.BLUE);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
Now we have a blue tint paint. Any shape drawn will tint the already existing, non-transparent pixels blue in the
area of the shape.
Section 90.3: Apply a radial mask (vignette) to a bitmap using
PorterDuXfermode
/**
* Apply a radial mask (vignette, i.e. fading to black at the borders) to a bitmap
* @param imageToApplyMaskTo Bitmap to modify
*/
public static void radialMask(final Bitmap imageToApplyMaskTo) {
Canvas canvas = new Canvas(imageToApplyMaskTo);
final float centerX = imageToApplyMaskTo.getWidth() * 0.5f;
final float centerY = imageToApplyMaskTo.getHeight() * 0.5f;
final float radius = imageToApplyMaskTo.getHeight() * 0.7f;
RadialGradient gradient = new RadialGradient(centerX, centerY, radius,
0x00000000, 0xFF000000, android.graphics.Shader.TileMode.CLAMP);
GoalKicker.com – Android™ Notes for Professionals 580
Paint p = new Paint();
p.setShader(gradient);
p.setColor(0xFF000000);
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
canvas.drawRect(0, 0, imageToApplyMaskTo.getWidth(), imageToApplyMaskTo.getHeight(), p);
}
GoalKicker.com – Android™ Notes for Professionals 581
Chapter 91: Menu
Parameter Description
inflate(int menuRes, Menu menu) Inflate a menu hierarchy from the specified XML resource.
getMenuInflater () Returns a MenuInflater with this context.
onCreateOptionsMenu (Menu menu) Initialize the contents of the Activity's standard options menu. You
should place your menu items in to menu.
onOptionsItemSelected (MenuItem item) This method is called whenever an item in your options menu is
selected
Section 91.1: Options menu with dividers
In Android there is a default options menu, which can take a number of options. If a larger number of options
needs to be displayed, then it makes sense to group those options in order to maintain clarity. Options can be
grouped by putting dividers (i.e. horizontal lines) between them. In order to allow for dividers, the following theme
can be used:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:dropDownListViewStyle">@style/PopupMenuListView</item>
</style>
<style name="PopupMenuListView" parent="@style/Widget.AppCompat.ListView.DropDown">
<item name="android:divider">@color/black</item>
<item name="android:dividerHeight">1dp</item>
</style>
By changing the theme, dividers can be added to a menu.
Section 91.2: Apply custom font to Menu
public static void applyFontToMenu(Menu m, Context mContext){
for(int i=0;i<m.size();i++) {
applyFontToMenuItem(m.getItem(i),mContext);
}
}
public static void applyFontToMenuItem(MenuItem mi, Context mContext) {
if(mi.hasSubMenu())
for(int i=0;i<mi.getSubMenu().size();i++) {
applyFontToMenuItem(mi.getSubMenu().getItem(i),mContext);
}
Typeface font = Typeface.createFromAsset(mContext.getAssets(), "fonts/yourCustomFont.ttf");
SpannableString mNewTitle = new SpannableString(mi.getTitle());
mNewTitle.setSpan(new CustomTypefaceSpan("", font, mContext), 0, mNewTitle.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
mi.setTitle(mNewTitle);
}
and then in the Activity:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
applyFontToMenu(menu,this);
GoalKicker.com – Android™ Notes for Professionals 582
return true;
}
Section 91.3: Creating a Menu in an Activity
To define your own menu, create an XML file inside your project's res/menu/ directory and build the menu with the
following elements:
<menu> : Defines a Menu, which holds all the menu items.
<item> : Creates a MenuItem, which represents a single item in a menu. We can also create a nested element
in order to create a submenu.
Step 1:
Create your own xml file as the following:
In res/menu/main_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/aboutMenu"
android:title="About" />
<item
android:id="@+id/helpMenu"
android:title="Help" />
<item
android:id="@+id/signOutMenu"
android:title="Sign Out" />
</menu>
Step 2:
To specify the options menu, override onCreateOptionsMenu() in your activity.
In this method, you can inflate your menu resource (defined in your XML file i.e., res/menu/main_menu.xml)
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
When the user selects an item from the options menu, the system calls your activity's overridden
onOptionsItemSelected() method.
This method passes the MenuItem selected.
You can identify the item by calling getItemId(), which returns the unique ID for the menu item (defined by
the android:id attribute in the menu resource - res/menu/main_menu.xml)*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.aboutMenu:
Log.d(TAG, "Clicked on About!");
GoalKicker.com – Android™ Notes for Professionals 583
// Code for About goes here
return true;
case R.id.helpMenu:
Log.d(TAG, "Clicked on Help!");
// Code for Help goes here
return true;
case R.id.signOutMenu:
Log.d(TAG, "Clicked on Sign Out!");
// SignOut method call goes here
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Wrapping up!
Your Activity code should look like below:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "mytag";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.aboutMenu:
Log.d(TAG, "Clicked on About!");
// Code for About goes here
return true;
case R.id.helpMenu:
Log.d(TAG, "Clicked on Help!");
// Code for Help goes here
return true;
case R.id.signOutMenu:
Log.d(TAG, "User signed out");
// SignOut method call goes here
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Screenshot of how your own Menu looks:
GoalKicker.com – Android™ Notes for Professionals 584
GoalKicker.com – Android™ Notes for Professionals 585
Chapter 92: Picasso
Picasso is an image library for Android. It's created and maintained by Square. It simplifies the process of displaying
images from external locations. The library handles every stage of the process, from the initial HTTP request to the
caching of the image. In many cases, only a few lines of code are required to implement this neat library.
Section 92.1: Adding Picasso Library to your Android Project
From the official documentation:
Gradle.
dependencies {
compile "com.squareup.picasso:picasso:2.5.2"
}
Maven:
<dependency>
<groupId>com.squareup.picasso</groupId>
<artifactId>picasso</artifactId>
<version>2.5.2</version>
</dependency>
Section 92.2: Circular Avatars with Picasso
Here is an example Picasso Circle Transform class based on the original, with the addition of a thin border, and also
includes functionality for an optional separator for stacking:
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import com.squareup.picasso.Transformation;
public class CircleTransform implements Transformation {
boolean mCircleSeparator = false;
public CircleTransform(){
}
public CircleTransform(boolean circleSeparator){
mCircleSeparator = circleSeparator;
}
@Override
public Bitmap transform(Bitmap source) {
int size = Math.min(source.getWidth(), source.getHeight());
int x = (source.getWidth() - size) / 2;
int y = (source.getHeight() - size) / 2;
Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size);
if (squaredBitmap != source) {
source.recycle();
GoalKicker.com – Android™ Notes for Professionals 586
}
Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig());
Canvas canvas = new Canvas(bitmap);
BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
Paint.FILTER_BITMAP_FLAG);
paint.setShader(shader);
float r = size/2f;
canvas.drawCircle(r, r, r-1, paint);
// Make the thin border:
Paint paintBorder = new Paint();
paintBorder.setStyle(Style.STROKE);
paintBorder.setColor(Color.argb(84,0,0,0));
paintBorder.setAntiAlias(true);
paintBorder.setStrokeWidth(1);
canvas.drawCircle(r, r, r-1, paintBorder);
// Optional separator for stacking:
if (mCircleSeparator) {
Paint paintBorderSeparator = new Paint();
paintBorderSeparator.setStyle(Style.STROKE);
paintBorderSeparator.setColor(Color.parseColor("#ffffff"));
paintBorderSeparator.setAntiAlias(true);
paintBorderSeparator.setStrokeWidth(4);
canvas.drawCircle(r, r, r+1, paintBorderSeparator);
}
squaredBitmap.recycle();
return bitmap;
}
@Override
public String key() {
return "circle";
}
}
Here is how to use it when loading an image (assuming this is an Activity Context, and url is a String with the url of
the image to load):
ImageView ivAvatar = (ImageView) itemView.findViewById(R.id.avatar);
Picasso.with(this).load(url)
.fit()
.transform(new CircleTransform())
.into(ivAvatar);
Result:
GoalKicker.com – Android™ Notes for Professionals 587
For use with the separator, give true to the constructor for the top image:
ImageView ivAvatar = (ImageView) itemView.findViewById(R.id.avatar);
Picasso.with(this).load(url)
.fit()
.transform(new CircleTransform(true))
.into(ivAvatar);
Result (two ImageViews in a FrameLayout):
Section 92.3: Placeholder and Error Handling
Picasso supports both download and error placeholders as optional features. Its also provides callbacks for
handling the download result.
Picasso.with(context)
.load("YOUR IMAGE URL HERE")
.placeholder(Your Drawable Resource) //this is optional the image to display while the url image
is downloading
.error(Your Drawable Resource) //this is also optional if some error has occurred in
downloading the image this image would be displayed
.into(imageView, new Callback(){
@Override
public void onSuccess() {}
@Override
public void onError() {}
});
A request will be retried three times before the error placeholder is shown.
Section 92.4: Re-sizing and Rotating
Picasso.with(context)
.load("YOUR IMAGE URL HERE")
.placeholder(DRAWABLE RESOURCE) // optional
.error(DRAWABLE RESOURCE) // optional
.resize(width, height) // optional
.rotate(degree) // optional
.into(imageView);
GoalKicker.com – Android™ Notes for Professionals 588
Section 92.5: Disable cache in Picasso
Picasso.with(context)
.load(uri)
.networkPolicy(NetworkPolicy.NO_CACHE)
.memoryPolicy(MemoryPolicy.NO_CACHE)
.placeholder(R.drawable.placeholder)
.into(imageView);
Section 92.6: Using Picasso as ImageGetter for Html.fromHtml
Using Picasso as ImageGetter for Html.fromHtml
public class PicassoImageGetter implements Html.ImageGetter {
private TextView textView;
private Picasso picasso;
public PicassoImageGetter(@NonNull Picasso picasso, @NonNull TextView textView) {
this.picasso = picasso;
this.textView = textView;
}
@Override
public Drawable getDrawable(String source) {
Log.d(PicassoImageGetter.class.getName(), "Start loading url " + source);
BitmapDrawablePlaceHolder drawable = new BitmapDrawablePlaceHolder();
picasso
.load(source)
.error(R.drawable.connection_error)
.into(drawable);
return drawable;
}
private class BitmapDrawablePlaceHolder extends BitmapDrawable implements Target {
protected Drawable drawable;
@Override
public void draw(final Canvas canvas) {
if (drawable != null) {
checkBounds();
drawable.draw(canvas);
}
}
public void setDrawable(@Nullable Drawable drawable) {
if (drawable != null) {
this.drawable = drawable;
checkBounds();
}
}
private void checkBounds() {
float defaultProportion = (float) drawable.getIntrinsicWidth() / (float)
drawable.getIntrinsicHeight();
GoalKicker.com – Android™ Notes for Professionals 589
int width = Math.min(textView.getWidth(), drawable.getIntrinsicWidth());
int height = (int) ((float) width / defaultProportion);
if (getBounds().right != textView.getWidth() || getBounds().bottom != height) {
setBounds(0, 0, textView.getWidth(), height); //set to full width
int halfOfPlaceHolderWidth = (int) ((float) getBounds().right / 2f);
int halfOfImageWidth = (int) ((float) width / 2f);
drawable.setBounds(
halfOfPlaceHolderWidth - halfOfImageWidth, //centering an image
0,
halfOfPlaceHolderWidth + halfOfImageWidth,
height);
textView.setText(textView.getText()); //refresh text
}
}
//------------------------------------------------------------------//
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setDrawable(new BitmapDrawable(Application.getContext().getResources(), bitmap));
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
setDrawable(errorDrawable);
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
setDrawable(placeHolderDrawable);
}
//------------------------------------------------------------------//
}
}
The usage is simple:
Html.fromHtml(textToParse, new PicassoImageGetter(picasso, textViewTarget), null);
Section 92.7: Cancelling Image Requests using Picasso
In certain cases we need to cancel an image download request in Picasso before the download has completed.
This could happen for various reasons, for example if the parent view transitioned to some other view before the
image download could be completed.
In this case, you can cancel the image download request using the cancelRequest() method:
ImageView imageView;
//......
GoalKicker.com – Android™ Notes for Professionals 590
Picasso.with(imageView.getContext()).cancelRequest(imageView);
Section 92.8: Loading Image from external Storage
String filename = "image.png";
String imagePath = getExternalFilesDir() + "/" + filename;
Picasso.with(context)
.load(new File(imagePath))
.into(imageView);
Section 92.9: Downloading image as Bitmap using Picasso
If you want to Download image as Bitmap using Picasso following code will help you:
Picasso.with(mContext)
.load(ImageUrl)
.into(new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// Todo: Do something with your bitmap here
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
Section 92.10: Try oine disk cache first, then go online and
fetch the image
first add the OkHttp to the gradle build file of the app module
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
Then make a class extending Application
import android.app.Application;
import com.squareup.picasso.OkHttpDownloader;
import com.squareup.picasso.Picasso;
public class Global extends Application {
@Override
public void onCreate() {
super.onCreate();
Picasso.Builder builder = new Picasso.Builder(this);
builder.downloader(new OkHttpDownloader(this,Integer.MAX_VALUE));
Picasso built = builder.build();
built.setIndicatorsEnabled(true);
built.setLoggingEnabled(true);
GoalKicker.com – Android™ Notes for Professionals 591
Picasso.setSingletonInstance(built);
}
}
add it to the Manifest file as follows :
<application
android:name=".Global"
.. >
</application>
Normal Usage
Picasso.with(getActivity())
.load(imageUrl)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView, new Callback() {
@Override
public void onSuccess() {
//Offline Cache hit
}
@Override
public void onError() {
//Try again online if cache failed
Picasso.with(getActivity())
.load(imageUrl)
.error(R.drawable.header)
.into(imageView, new Callback() {
@Override
public void onSuccess() {
//Online download
}
@Override
public void onError() {
Log.v("Picasso","Could not fetch image");
}
});
}
});
Link to original answer
GoalKicker.com – Android™ Notes for Professionals 592
Chapter 93: RoboGuice
Section 93.1: Simple example
RoboGuice is a framework that brings the simplicity and ease of Dependency Injection to Android, using Google's
own Guice library.
@ContentView(R.layout.main)
class RoboWay extends RoboActivity {
@InjectView(R.id.name) TextView name;
@InjectView(R.id.thumbnail) ImageView thumbnail;
@InjectResource(R.drawable.icon) Drawable icon;
@InjectResource(R.string.app_name) String myName;
@Inject LocationManager loc;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
name.setText( "Hello, " + myName );
}
}
Section 93.2: Installation for Gradle Projects
Add the following pom to the dependencies section of your gradle build file :
project.dependencies {
compile 'org.roboguice:roboguice:3.+'
provided 'org.roboguice:roboblender:3.+'
}
Section 93.3: @ContentView annotation
The @ContentView annotation can be used to further alleviate development of activities and replace the
setContentView statement :
@ContentView(R.layout.myactivity_layout)
public class MyActivity extends RoboActivity {
@InjectView(R.id.text1) TextView textView;
@Override
protected void onCreate( Bundle savedState ) {
textView.setText("Hello!");
}
}
Section 93.4: @InjectResource annotation
You can inject any type of resource, Strings, Animations, Drawables, etc.
To inject your first resource into an activity, you'll need to:
Inherit from RoboActivity
Annotate your resources with @InjectResource
Example
GoalKicker.com – Android™ Notes for Professionals 593
@InjectResource(R.string.app_name) String name;
@InjectResource(R.drawable.ic_launcher) Drawable icLauncher;
@InjectResource(R.anim.my_animation) Animation myAnimation;
Section 93.5: @InjectView annotation
You can inject any view using the @InjectView annotation:
You'll need to:
Inherit from RoboActivity
Set your content view
Annotate your views with @InjectView
Example
@InjectView(R.id.textView1) TextView textView1;
@InjectView(R.id.textView2) TextView textView2;
@InjectView(R.id.imageView1) ImageView imageView1;
Section 93.6: Introduction to RoboGuice
RoboGuice is a framework that brings the simplicity and ease of Dependency Injection to Android, using Google's
own Guice library.
RoboGuice 3 slims down your application code. Less code means fewer opportunities for bugs. It also makes your
code easier to follow -- no longer is your code littered with the mechanics of the Android platform, but now it can
focus on the actual logic unique to your application.
To give you an idea, take a look at this simple example of a typical Android Activity:
class AndroidWay extends Activity {
TextView name;
ImageView thumbnail;
LocationManager loc;
Drawable icon;
String myName;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
name = (TextView) findViewById(R.id.name);
thumbnail = (ImageView) findViewById(R.id.thumbnail);
loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);
icon = getResources().getDrawable(R.drawable.icon);
myName = getString(R.string.app_name);
name.setText( "Hello, " + myName );
}
}
This example is 19 lines of code. If you're trying to read through onCreate(), you have to skip over 5 lines of
boilerplate initialization to find the only one that really matters: name.setText(). And complex activities can end up
with a lot more of this sort of initialization code.
GoalKicker.com – Android™ Notes for Professionals 594
Compare this to the same app, written using RoboGuice:
@ContentView(R.layout.main)
class RoboWay extends RoboActivity {
@InjectView(R.id.name) TextView name;
@InjectView(R.id.thumbnail) ImageView thumbnail;
@InjectResource(R.drawable.icon) Drawable icon;
@InjectResource(R.string.app_name) String myName;
@Inject LocationManager loc;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
name.setText( "Hello, " + myName );
}
}
RoboGuice's goal is to make your code be about your app, rather than be about all the initialization and lifecycle
code you typically have to maintain in Android.
Annotations:
@ContentView annotation:
The @ContentView annotation can be used to further alleviate development of activities and replace the
setContentView statement :
@ContentView(R.layout.myactivity_layout)
public class MyActivity extends RoboActivity {
@InjectView(R.id.text1) TextView textView;
@Override
protected void onCreate( Bundle savedState ) {
textView.setText("Hello!");
}
}
@InjectResource annotation:
First you need an Activity that inherits from RoboActivity. Then, assuming that you have an animation
my_animation.xml in your res/anim folder, you can now reference it with an annotation:
public class MyActivity extends RoboActivity {
@InjectResource(R.anim.my_animation) Animation myAnimation;
// the rest of your code
}
@Inject annotation:
You make sure your activity extends from RoboActivity and annotate your System service member with @Inject.
Roboguice will do the rest.
class MyActivity extends RoboActivity {
@Inject Vibrator vibrator;
@Inject NotificationManager notificationManager;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GoalKicker.com – Android™ Notes for Professionals 595
// we can use the instances directly!
vibrator.vibrate(1000L); // RoboGuice took care of the getSystemService(VIBRATOR_SERVICE)
notificationManager.cancelAll();
In addition to Views, Resources, Services, and other android-specific things, RoboGuice can inject Plain Old Java
Objects. By default Roboguice will call a no argument constructor on your POJO
class MyActivity extends RoboActivity {
@Inject Foo foo; // this will basically call new Foo();
}
GoalKicker.com – Android™ Notes for Professionals 596
Chapter 94: ACRA
Parameter Description
@ReportCrashes Defines the ACRA settings such as where it is to be reported, custom content, etc
formUri the path to the file that reports the crash
Section 94.1: ACRAHandler
Example Application-extending class for handling the reporting:
@ReportsCrashes(
formUri = "https://backend-of-your-choice.com/",//Non-password protected.
customReportContent = { /* */ReportField.APP_VERSION_NAME,
ReportField.PACKAGE_NAME,ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL,ReportField.LOGCAT },
mode = ReportingInteractionMode.TOAST,
resToastText = R.string.crash
)
public class ACRAHandler extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
final ACRAConfiguration config = new ConfigurationBuilder(this)
.build();
// Initialise ACRA
ACRA.init(this, config);
}
}
Section 94.2: Example manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<!-- etc -->
>
<!-- Internet is required. READ_LOGS are to ensure that the Logcat is transmitted-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<application
android:allowBackup="true"
android:name=".ACRAHandler"<!-- Activates ACRA on startup -->
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
GoalKicker.com – Android™ Notes for Professionals 597
<!-- Activities -->
</application>
</manifest>
Section 94.3: Installation
Maven
<dependency>
<groupId>ch.acra</groupId>
<artifactId>acra</artifactId>
<version>4.9.2</version>
<type>aar</type>
</dependency>
Gradle
compile 'ch.acra:acra:4.9.2'
GoalKicker.com – Android™ Notes for Professionals 598
Chapter 95: Parcelable
Parcelable is an Android specific interface where you implement the serialization yourself. It was created to be far
more efficient that Serializable, and to get around some problems with the default Java serialization scheme.
Section 95.1: Making a custom object Parcelable
/**
* Created by Alex Sullivan on 7/21/16.
*/
public class Foo implements Parcelable
{
private final int myFirstVariable;
private final String mySecondVariable;
private final long myThirdVariable;
public Foo(int myFirstVariable, String mySecondVariable, long myThirdVariable)
{
this.myFirstVariable = myFirstVariable;
this.mySecondVariable = mySecondVariable;
this.myThirdVariable = myThirdVariable;
}
// Note that you MUST read values from the parcel IN THE SAME ORDER that
// values were WRITTEN to the parcel! This method is our own custom method
// to instantiate our object from a Parcel. It is used in the Parcelable.Creator variable we
declare below.
public Foo(Parcel in)
{
this.myFirstVariable = in.readInt();
this.mySecondVariable = in.readString();
this.myThirdVariable = in.readLong();
}
// The describe contents method can normally return 0. It's used when
// the parceled object includes a file descriptor.
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeInt(myFirstVariable);
dest.writeString(mySecondVariable);
dest.writeLong(myThirdVariable);
}
// Note that this seemingly random field IS NOT OPTIONAL. The system will
// look for this variable using reflection in order to instantiate your
// parceled object when read from an Intent.
public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>()
{
// This method is used to actually instantiate our custom object
// from the Parcel. Convention dictates we make a new constructor that
// takes the parcel in as its only argument.
public Foo createFromParcel(Parcel in)
{
GoalKicker.com – Android™ Notes for Professionals 599
return new Foo(in);
}
// This method is used to make an array of your custom object.
// Declaring a new array with the provided size is usually enough.
public Foo[] newArray(int size)
{
return new Foo[size];
}
};
}
Section 95.2: Parcelable object containing another Parcelable
object
An example of a class that contains a parcelable class inside:
public class Repository implements Parcelable {
private String name;
private Owner owner;
private boolean isPrivate;
public Repository(String name, Owner owner, boolean isPrivate) {
this.name = name;
this.owner = owner;
this.isPrivate = isPrivate;
}
protected Repository(Parcel in) {
name = in.readString();
owner = in.readParcelable(Owner.class.getClassLoader());
isPrivate = in.readByte() != 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeParcelable(owner, flags);
dest.writeByte((byte) (isPrivate ? 1 : 0));
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Repository> CREATOR = new Creator<Repository>() {
@Override
public Repository createFromParcel(Parcel in) {
return new Repository(in);
}
@Override
public Repository[] newArray(int size) {
return new Repository[size];
}
};
//getters and setters
public String getName() {
GoalKicker.com – Android™ Notes for Professionals 600
return name;
}
public void setName(String name) {
this.name = name;
}
public Owner getOwner() {
return owner;
}
public void setOwner(Owner owner) {
this.owner = owner;
}
public boolean isPrivate() {
return isPrivate;
}
public void setPrivate(boolean isPrivate) {
this.isPrivate = isPrivate;
}
}
Owner is just a normal parcelable class.
Section 95.3: Using Enums with Parcelable
/**
* Created by Nick Cardoso on 03/08/16.
* This is not a complete parcelable implementation, it only highlights the easiest
* way to read and write your Enum values to your parcel
*/
public class Foo implements Parcelable {
private final MyEnum myEnumVariable;
private final MyEnum mySaferEnumVariableExample;
public Foo(Parcel in) {
//the simplest way
myEnumVariable = MyEnum.valueOf( in.readString() );
//with some error checking
try {
mySaferEnumVariableExample= MyEnum.valueOf( in.readString() );
} catch (IllegalArgumentException e) { //bad string or null value
mySaferEnumVariableExample= MyEnum.DEFAULT;
}
}
...
@Override
public void writeToParcel(Parcel dest, int flags) {
//the simple way
dest.writeString(myEnumVariable.name());
//avoiding NPEs with some error checking
GoalKicker.com – Android™ Notes for Professionals 601
dest.writeString(mySaferEnumVariableExample == null? null :
mySaferEnumVariableExample.name());
}
}
public enum MyEnum {
VALUE_1,
VALUE_2,
DEFAULT
}
This is preferable to (for example) using an ordinal, because inserting new values into your enum will not affect
previously stored values
GoalKicker.com – Android™ Notes for Professionals 602
Chapter 96: Retrofit2
The official Retrofit page describes itself as
A type-safe REST client for Android and Java.
Retrofit turns your REST API into a Java interface. It uses annotations to describe HTTP requests, URL parameter
replacement and query parameter support is integrated by default. Additionally, it provides functionality for
multipart request body and file uploads.
Section 96.1: A Simple GET Request
We are going to be showing how to make a GET request to an API that responds with a JSON object or a JSON array.
The first thing we need to do is add the Retrofit and GSON Converter dependencies to our module's gradle file.
Add the dependencies for retrofit library as described in the Remarks section.
Example of expected JSON object:
{
"deviceId": "56V56C14SF5B4SF",
"name": "Steven",
"eventCount": 0
}
Example of JSON array:
[
{
"deviceId": "56V56C14SF5B4SF",
"name": "Steven",
"eventCount": 0
},
{
"deviceId": "35A80SF3QDV7M9F",
"name": "John",
"eventCount": 2
}
]
Example of corresponding model class:
public class Device
{
@SerializedName("deviceId")
public String id;
@SerializedName("name")
public String name;
@SerializedName("eventCount")
public int eventCount;
}
The @SerializedName annotations here are from the GSON library and allows us to serialize and deserialize this
class to JSON using the serialized name as the keys. Now we can build the interface for the API that will actually
GoalKicker.com – Android™ Notes for Professionals 603
fetch the data from the server.
public interface DeviceAPI
{
@GET("device/{deviceId}")
Call<Device> getDevice (@Path("deviceId") String deviceID);
@GET("devices")
Call<List<Device>> getDevices();
}
There's a lot going on here in a pretty compact space so let's break it down:
The @GET annotation comes from Retrofit and tells the library that we're defining a GET request.
The path in the parentheses is the endpoint that our GET request should hit (we'll set the base url a little
later).
The curly-brackets allow us to replace parts of the path at run time so we can pass arguments.
The function we're defining is called getDevice and takes the device id we want as an argument.
The @PATH annotation tells Retrofit that this argument should replace the "deviceId" placeholder in the path.
The function returns a Call object of type Device.
Creating a wrapper class:
Now we will make a little wrapper class for our API to keep the Retrofit initialization code wrapped up nicely.
public class DeviceAPIHelper
{
public final DeviceAPI api;
private DeviceAPIHelper ()
{
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
api = retrofit.create(DeviceAPI.class);
}
}
This class creates a GSON instance to be able to parse the JSON response, creates a Retrofit instance with our base
url and a GSONConverter and then creates an instance of our API.
Calling the API:
// Getting a JSON object
Call<Device> callObject = api.getDevice(deviceID);
callObject.enqueue(new Callback<Response<Device>>()
{
@Override
public void onResponse (Call<Device> call, Response<Device> response)
{
if (response.isSuccessful())
{
Device device = response.body();
}
}
GoalKicker.com – Android™ Notes for Professionals 604
@Override
public void onFailure (Call<Device> call, Throwable t)
{
Log.e(TAG, t.getLocalizedMessage());
}
});
// Getting a JSON array
Call<List<Device>> callArray = api.getDevices();
callArray.enqueue(new Callback<Response<List<Device>>()
{
@Override
public void onResponse (Call<List<Device>> call, Response<List<Device>> response)
{
if (response.isSuccessful())
{
List<Device> devices = response.body();
}
}
@Override
public void onFailure (Call<List<Device>> call, Throwable t)
{
Log.e(TAG, t.getLocalizedMessage());
}
});
This uses our API interface to create a Call<Device> object and to create a Call<List<Device>> respectively.
Calling enqueue tells Retrofit to make that call on a background thread and return the result to the callback that
we're creating here.
Note: Parsing a JSON array of primitive objects (like String, Integer, Boolean, and Double) is similar to parsing a JSON
array. However, you don't need your own model class. You can get the array of Strings for example by having the
return type of the call as Call<List<String>>.
Section 96.2: Debugging with Stetho
Add the following dependencies to your application.
compile 'com.facebook.stetho:stetho:1.5.0'
compile 'com.facebook.stetho:stetho-okhttp3:1.5.0'
In your Application class' onCreate method, call the following.
Stetho.initializeWithDefaults(this);
When creating your Retrofit instance, create a custom OkHttp instance.
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.addNetworkInterceptor(new StethoInterceptor());
Then set this custom OkHttp instance in the Retrofit instance.
Retrofit retrofit = new Retrofit.Builder()
// ...
.client(clientBuilder.build())
.build();
GoalKicker.com – Android™ Notes for Professionals 605
Now connect your phone to your computer, launch the app, and type chrome://inspect into your Chrome
browser. Retrofit network calls should now show up for you to inspect.
Section 96.3: Add logging to Retrofit2
Retrofit requests can be logged using an intercepter. There are several levels of detail available: NONE, BASIC,
HEADERS, BODY. See Github project here.
1. Add dependency to build.gradle:
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
2. Add logging interceptor when creating Retrofit:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(LoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.addInterceptor(loggingInterceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Exposing the logs in the Terminal(Android Monitor) is something that should be avoided in the release version as it
may lead to unwanted exposing of critical information such as Auth Tokens etc.
To avoid the logs being exposed in the run time, check the following condition
if(BuildConfig.DEBUG){
//your interfector code here
}
For example:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
if(BuildConfig.DEBUG){
//print the logs in this case
loggingInterceptor.setLevel(LoggingInterceptor.Level.BODY);
}else{
loggingInterceptor.setLevel(LoggingInterceptor.Level.NONE);
}
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.addInterceptor(loggingInterceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Section 96.4: A simple POST request with GSON
Sample JSON:
GoalKicker.com – Android™ Notes for Professionals 606
{
"id": "12345",
"type": "android"
}
Define your request:
public class GetDeviceRequest {
@SerializedName("deviceId")
private String mDeviceId;
public GetDeviceRequest(String deviceId) {
this.mDeviceId = deviceId;
}
public String getDeviceId() {
return mDeviceId;
}
}
Define your service (endpoints to hit):
public interface Service {
@POST("device")
Call<Device> getDevice(@Body GetDeviceRequest getDeviceRequest);
}
Define your singleton instance of the network client:
public class RestClient {
private static Service REST_CLIENT;
static {
setupRestClient();
}
private static void setupRestClient() {
// Define gson
Gson gson = new Gson();
// Define our client
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
REST_CLIENT = retrofit.create(Service.class);
}
public static Retrofit getRestClient() {
return REST_CLIENT;
}
GoalKicker.com – Android™ Notes for Professionals 607
}
Define a simple model object for the device:
public class Device {
@SerializedName("id")
private String mId;
@SerializedName("type")
private String mType;
public String getId() {
return mId;
}
public String getType() {
return mType;
}
}
Define controller to handle the requests for the device
public class DeviceController {
// Other initialization code here...
public void getDeviceFromAPI() {
// Define our request and enqueue
Call<Device> call = RestClient.getRestClient().getDevice(new GetDeviceRequest("12345"));
// Go ahead and enqueue the request
call.enqueue(new Callback<Device>() {
@Override
public void onSuccess(Response<Device> deviceResponse) {
// Take care of your device here
if (deviceResponse.isSuccess()) {
// Handle success
//delegate.passDeviceObject();
}
}
@Override
public void onFailure(Throwable t) {
// Go ahead and handle the error here
}
});
Section 96.5: Download a file from Server using Retrofit2
Interface declaration for downloading a file
public interface ApiInterface {
@GET("movie/now_playing")
Call<MovieResponse> getNowPlayingMovies(@Query("api_key") String apiKey, @Query("page") int
page);
GoalKicker.com – Android™ Notes for Professionals 608
// option 1: a resource relative to your base URL
@GET("resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();
// option 2: using a dynamic URL
@GET
Call<ResponseBody> downloadFileWithDynamicUrl(@Url String fileUrl);
}
The option 1 is used for downloading a file from Server which is having fixed URL. and option 2 is used to pass a
dynamic value as full URL to request call. This can be helpful when downloading files, which are dependent of
parameters like user or time.
Setup retrofit for making api calls
public class ServiceGenerator {
public static final String API_BASE_URL = "http://your.api-base.url/";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
public static <S> S createService(Class<S> serviceClass){
Retrofit retrofit = builder.client(httpClient.build()).build();
return retrofit.create(serviceClass);
}
}
Now, make implementation of api for downloading file from server
private void downloadFile(){
ApiInterface apiInterface = ServiceGenerator.createService(ApiInterface.class);
Call<ResponseBody> call = apiInterface.downloadFileWithFixedUrl();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()){
boolean writtenToDisk = writeResponseBodyToDisk(response.body());
Log.d("File download was a success? ", String.valueOf(writtenToDisk));
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
And after getting response in the callback, code some standard IO for saving file to disk. Here is the code:
private boolean writeResponseBodyToDisk(ResponseBody body) {
GoalKicker.com – Android™ Notes for Professionals 609
try {
// todo change the file location/name according to your needs
File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator +
"Future Studio Icon.png");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d("File Download: " , fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
Note we have specified ResponseBody as return type, otherwise Retrofit will try to parse and convert it, which
doesn't make sense when you are downloading file.
If you want more on Retrofit stuffs, got to this link as it is very useful. [1]:
https://futurestud.io/blog/retrofit-getting-started-and-android-client
Section 96.6: Upload multiple file using Retrofit as multipart
Once you have setup the Retrofit environment in your project, you can use the following example that
demonstrates how to upload multiple files using Retrofit:
GoalKicker.com – Android™ Notes for Professionals 610
private void mulipleFileUploadFile(Uri[] fileUri) {
OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient clientWith30sTimeout = okHttpClient.newBuilder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL_BASE)
.addConverterFactory(new MultiPartConverter())
.client(clientWith30sTimeout)
.build();
WebAPIService service = retrofit.create(WebAPIService.class); //here is the interface which you
have created for the call service
Map<String, okhttp3.RequestBody> maps = new HashMap<>();
if (fileUri!=null && fileUri.length>0) {
for (int i = 0; i < fileUri.length; i++) {
String filePath = getRealPathFromUri(fileUri[i]);
File file1 = new File(filePath);
if (filePath != null && filePath.length() > 0) {
if (file1.exists()) {
okhttp3.RequestBody requestFile =
okhttp3.RequestBody.create(okhttp3.MediaType.parse("multipart/form-data"), file1);
String filename = "imagePath" + i; //key for upload file like : imagePath0
maps.put(filename + "\"; filename=\"" + file1.getName(), requestFile);
}
}
}
}
String descriptionString = " string request";//
//hear is the your json request
Call<String> call = service.postFile(maps, descriptionString);
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call,
Response<String> response) {
Log.i(LOG_TAG, "success");
Log.d("body==>", response.body().toString() + "");
Log.d("isSuccessful==>", response.isSuccessful() + "");
Log.d("message==>", response.message() + "");
Log.d("raw==>", response.raw().toString() + "");
Log.d("raw().networkResponse()", response.raw().networkResponse().toString() + "");
}
@Override
public void onFailure(Call<String> call, Throwable t) {
Log.e(LOG_TAG, t.getMessage());
}
});
}
public String getRealPathFromUri(final Uri uri) { // function for file path from uri,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
DocumentsContract.isDocumentUri(mContext, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
GoalKicker.com – Android™ Notes for Professionals 611
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(mContext, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(mContext, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(mContext, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
Following is the interface
public interface WebAPIService {
@Multipart
@POST("main.php")
Call<String> postFile(@PartMap Map<String,RequestBody> Files, @Part("json") String
description);
}
GoalKicker.com – Android™ Notes for Professionals 612
Section 96.7: Retrofit with OkHttp interceptor
This example shows how to use a request interceptor with OkHttp. This has numerous use cases such as:
Adding universal header to the request. E.g. authenticating a request
Debugging networked applications
Retrieving raw response
Logging network transaction etc.
Set custom user agent
Retrofit.Builder builder = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com/");
if (!TextUtils.isEmpty(githubToken)) {
// `githubToken`: Access token for GitHub
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request newReq = request.newBuilder()
.addHeader("Authorization", format("token %s", githubToken))
.build();
return chain.proceed(newReq);
}
}).build();
builder.client(client);
}
return builder.build().create(GithubApi.class);
See OkHttp topic for more details.
Section 96.8: Header and Body: an Authentication Example
The @Header and @Body annotations can be placed into the method signatures and Retrofit will automatically create
them based on your models.
public interface MyService {
@POST("authentication/user")
Call<AuthenticationResponse> authenticateUser(@Body AuthenticationRequest request,
@Header("Authorization") String basicToken);
}
AuthenticaionRequest is our model, a POJO, containing the information the server requires. For this example, our
server wants the client key and secret.
public class AuthenticationRequest {
String clientKey;
String clientSecret;
}
Notice that in @Header("Authorization") we are specifying we are populating the Authorization header. The other
headers will be populated automatically since Retrofit can infer what they are based on the type of objects we are
sending and expecting in return.
GoalKicker.com – Android™ Notes for Professionals 613
We create our Retrofit service somewhere. We make sure to use HTTPS.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https:// some example site")
.client(client)
.build();
MyService myService = retrofit.create(MyService.class)
Then we can use our service.
AuthenticationRequest request = new AuthenticationRequest();
request.setClientKey(getClientKey());
request.setClientSecret(getClientSecret());
String basicToken = "Basic " + token;
myService.authenticateUser(request, basicToken);
Section 96.9: Uploading a file via Multipart
Declare your interface with Retrofit2 annotations:
public interface BackendApiClient {
@Multipart
@POST("/uploadFile")
Call<RestApiDefaultResponse> uploadPhoto(@Part("file\"; filename=\"photo.jpg\" ") RequestBody
photo);
}
Where RestApiDefaultResponse is a custom class containing the response.
Building the implementation of your API and enqueue the call:
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://<yourhost>/")
.client(okHttpClient)
.build();
BackendApiClient apiClient = retrofit.create(BackendApiClient.class);
RequestBody reqBody = RequestBody.create(MediaType.parse("image/jpeg"), photoFile);
Call<RestApiDefaultResponse> call = apiClient.uploadPhoto(reqBody);
call.enqueue(<your callback function>);
Section 96.10: Retrofit 2 Custom Xml Converter
Adding dependencies into the build.gradle file.
dependencies {
....
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile ('com.thoughtworks.xstream:xstream:1.4.7') {
exclude group: 'xmlpull', module: 'xmlpull'
}
....
}
Then create Converter Factory
GoalKicker.com – Android™ Notes for Professionals 614
public class XStreamXmlConverterFactory extends Converter.Factory {
/** Create an instance using a default {@link com.thoughtworks.xstream.XStream} instance for
conversion. */
public static XStreamXmlConverterFactory create() {
return create(new XStream());
}
/** Create an instance using {@code xStream} for conversion. */
public static XStreamXmlConverterFactory create(XStream xStream) {
return new XStreamXmlConverterFactory(xStream);
}
private final XStream xStream;
private XStreamXmlConverterFactory(XStream xStream) {
if (xStream == null) throw new NullPointerException("xStream == null");
this.xStream = xStream;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
if (!(type instanceof Class)) {
return null;
}
Class<?> cls = (Class<?>) type;
return new XStreamXmlResponseBodyConverter<>(cls, xStream);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if (!(type instanceof Class)) {
return null;
}
return new XStreamXmlRequestBodyConverter<>(xStream);
}
}
create a class to handle the body request.
final class XStreamXmlResponseBodyConverter <T> implements Converter<ResponseBody, T> {
private final Class<T> cls;
private final XStream xStream;
XStreamXmlResponseBodyConverter(Class<T> cls, XStream xStream) {
this.cls = cls;
this.xStream = xStream;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
GoalKicker.com – Android™ Notes for Professionals 615
this.xStream.processAnnotations(cls);
Object object = this.xStream.fromXML(value.byteStream());
return (T) object;
}finally {
value.close();
}
}
}
create a class to handle the body response.
final class XStreamXmlRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/xml; charset=UTF-8");
private static final String CHARSET = "UTF-8";
private final XStream xStream;
XStreamXmlRequestBodyConverter(XStream xStream) {
this.xStream = xStream;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
try {
OutputStreamWriter osw = new OutputStreamWriter(buffer.outputStream(), CHARSET);
xStream.toXML(value, osw);
osw.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
So, this point we can send and receive any XML , We just need create XStream Annotations for the entities.
Then create a Retrofit instance:
XStream xs = new XStream(new DomDriver());
xs.autodetectAnnotations(true);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/")
.addConverterFactory(XStreamXmlConverterFactory.create(xs))
.client(client)
.build();
Section 96.11: Reading XML form URL with Retrofit 2
We will use retrofit 2 and SimpleXmlConverter to get xml data from url and parse to Java class.
Add dependency to Gradle script:
GoalKicker.com – Android™ Notes for Professionals 616
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-simplexml:2.1.0'
Create interface
Also create xml class wrapper in our case Rss class
public interface ApiDataInterface{
// path to xml link on web site
@GET (data/read.xml)
Call<Rss> getData();
}
Xml read function
private void readXmlFeed() {
try {
// base url - url of web site
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(http://www.google.com/)
.client(new OkHttpClient())
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
ApiDataInterface apiService = retrofit.create(ApiDataInterface.class);
Call<Rss> call = apiService.getData();
call.enqueue(new Callback<Rss>() {
@Override
public void onResponse(Call<Rss> call, Response<Rss> response) {
Log.e("Response success", response.message());
}
@Override
public void onFailure(Call<Rss> call, Throwable t) {
Log.e("Response fail", t.getMessage());
}
});
} catch (Exception e) {
Log.e("Exception", e.getMessage());
}
}
This is example of Java class with SimpleXML annotations
More about annotations SimpleXmlDocumentation
@Root (name = "rss")
GoalKicker.com – Android™ Notes for Professionals 617
public class Rss
{
public Rss() {
}
public Rss(String title, String description, String link, List<Item> item, String language) {
this.title = title;
this.description = description;
this.link = link;
this.item = item;
this.language = language;
}
@Element (name = "title")
private String title;
@Element(name = "description")
private String description;
@Element(name = "link")
private String link;
@ElementList (entry="item", inline=true)
private List<Item> item;
@Element(name = "language")
private String language;
GoalKicker.com – Android™ Notes for Professionals 618
Chapter 97: ButterKnife
Butterknife is a view binding tool that uses annotations to generate boilerplate code for us. This tool is developed
by Jake Wharton at Square and is essentially used to save typing repetitive lines of code like
findViewById(R.id.view) when dealing with views thus making our code look a lot cleaner.
To be clear, Butterknife is not a dependency injection library. Butterknife injects code at compile time. It is very
similar to the work done by Android Annotations.
Section 97.1: Configuring ButterKnife in your project
Configure your project-level build.gradle to include the android-apt plugin:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
}
}
Then, apply the android-apt plugin in your module-level build.gradle and add the ButterKnife dependencies:
apply plugin: 'android-apt'
android {
...
}
dependencies {
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}
Note: If you are using the new Jack compiler with version 2.2.0 or newer you do not need the android-apt plugin
and can instead replace apt with annotationProcessor when declaring the compiler dependency.
In order to use ButterKnife annotations you shouldn't forget about binding them in onCreate() of your Activities or
onCreateView() of your Fragments:
class ExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Binding annotations
ButterKnife.bind(this);
// ...
}
}
// Or
class ExampleFragment extends Fragment {
GoalKicker.com – Android™ Notes for Professionals 619
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(getContentView(), container, false);
// Binding annotations
ButterKnife.bind(this, view);
// ...
return view;
}
}
Snapshots of the development version are available in Sonatype's snapshots repository.
Below are the additional steps you'd have to take to use ButterKnife in a library project
To use ButterKnife in a library project, add the plugin to your project-level build.gradle:
buildscript {
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
}
}
…and then apply to your module by adding these lines on the top of your library-level build.gradle:
apply plugin: 'com.android.library'
// ...
apply plugin: 'com.jakewharton.butterknife'
Now make sure you use R2 instead of R inside all ButterKnife annotations.
class ExampleActivity extends Activity {
// Bind xml resource to their View
@BindView(R2.id.user) EditText username;
@BindView(R2.id.pass) EditText password;
// Binding resources from drawable,strings,dimens,colors
@BindString(R.string.choose) String choose;
@BindDrawable(R.drawable.send) Drawable send;
@BindColor(R.color.cyan) int cyan;
@BindDimen(R.dimen.margin) Float generalMargin;
// Listeners
@OnClick(R.id.submit)
public void submit(View view) {
// TODO submit data to server...
}
// bind with butterknife in onCreate
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
// TODO continue
}
GoalKicker.com – Android™ Notes for Professionals 620
}
Section 97.2: Unbinding views in ButterKnife
Fragments have a different view lifecycle than activities. When binding a fragment in onCreateView, set the views to
null in onDestroyView. Butter Knife returns an Unbinder instance when you call bind to do this for you. Call its
unbind method in the appropriate lifecycle callback.
An example:
public class MyFragment extends Fragment {
@BindView(R.id.textView) TextView textView;
@BindView(R.id.button) Button button;
private Unbinder unbinder;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
View view = inflater.inflate(R.layout.my_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
@Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
Note: Calling unbind() in onDestroyView() is not required, but recommended as it saves quite a bit of
memory if your app has a large backstack.
Section 97.3: Binding Listeners using ButterKnife
OnClick Listener:
@OnClick(R.id.login)
public void login(View view) {
// Additional logic
}
All arguments to the listener method are optional:
@OnClick(R.id.login)
public void login() {
// Additional logic
}
Specific type will be automatically casted:
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
GoalKicker.com – Android™ Notes for Professionals 621
Multiple IDs in a single binding for common event handling:
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}
Custom Views can bind to their own listeners by not specifying an ID:
public class CustomButton extends Button {
@OnClick
public void onClick() {
// TODO
}
}
Section 97.4: Android Studio ButterKnife Plugin
Android ButterKnife Zelezny
Plugin for generating ButterKnife injections from selected layout XMLs in activities/fragments/adapters.
Note : Make sure that you make the right click for your_xml_layou(R.layout.your_xml_layou) else the Generate
menu will not contain Butterknife injector option.
GoalKicker.com – Android™ Notes for Professionals 622
Link : Jetbrains Plugin Android ButterKnife Zelezny
Section 97.5: Binding Views using ButterKnife
we can annotate fields with @BindView and a view ID for Butter Knife to find and automatically cast the
corresponding view in our layout.
Binding Views
Binding Views in Activity
class ExampleActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
GoalKicker.com – Android™ Notes for Professionals 623
Binding Views in Fragments
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
private Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
// in fragments or non activity bindings we need to unbind the binding when view is about to be
destroyed
@Override
public void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
Binding Views in Dialogs
We can use ButterKnife.findById to find views on a View, Activity, or Dialog. It uses generics to infer the return
type and automatically performs the cast.
View view = LayoutInflater.from(context).inflate(R.layout.thing, null);
TextView firstName = ButterKnife.findById(view, R.id.first_name);
TextView lastName = ButterKnife.findById(view, R.id.last_name);
ImageView photo = ButterKnife.findById(view, R.id.photo);
Binding Views in ViewHolder
static class ViewHolder {
@BindView(R.id.title) TextView name;
@BindView(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
Binding Resources
Apart from being useful for binding views, one could also use ButterKnife to bind resources such as those defined
within strings.xml, drawables.xml, colors.xml, dimens.xml, etc.
public class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value)
field
@Override
public void onCreate(Bundle savedInstanceState) {
// ...
GoalKicker.com – Android™ Notes for Professionals 624
ButterKnife.bind(this);
}
}
Binding View Lists
You can group multiple views into a List or array. This is very helpful when we need to perform one action on
multiple views at once.
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
//The apply method allows you to act on all the views in a list at once.
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
//We can use Action and Setter interfaces allow specifying simple behavior.
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
@Override public void apply(View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
@Override public void set(View view, Boolean value, int index) {
view.setEnabled(value);
}
};
Optional Bindings
By default, both @Bind and listener bindings are required. An exception is thrown if the target view cannot be
found. But if we are not sure if a view will be there or not then we can add a @Nullable annotation to fields or the
@Optional annotation to methods to suppress this behavior and create an optional binding.
@Nullable
@BindView(R.id.might_not_be_there) TextView mightNotBeThere;
@Optional
@OnClick(R.id.maybe_missing)
void onMaybeMissingClicked() {
// TODO ...
}
GoalKicker.com – Android™ Notes for Professionals 625
Chapter 98: Volley
Volley is an Android HTTP library that was introduced by Google to make networking calls much simpler. By default
all the Volley network calls are made asynchronously, handling everything in a background thread and returning
the results in the foreground with use of callbacks. As fetching data over a network is one of the most common
tasks that is performed in any app, the Volley library was made to ease Android app development.
Section 98.1: Using Volley for HTTP requests
Add the gradle dependency in app-level build.gradle
compile 'com.android.volley:volley:1.0.0'
Also, add the android.permission.INTERNET permission to your app's manifest.
**Create Volley RequestQueue instance singleton in your Application **
public class InitApplication extends Application {
private RequestQueue queue;
private static InitApplication sInstance;
private static final String TAG = InitApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
Stetho.initializeWithDefaults(this);
}
public static synchronized InitApplication getInstance() {
return sInstance;
}
public <T> void addToQueue(Request<T> req, String tag) {
req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
getQueue().add(req);
}
public <T> void addToQueue(Request<T> req) {
req.setTag(TAG);
getQueue().add(req);
}
public void cancelPendingRequests(Object tag) {
if (queue != null) {
queue.cancelAll(tag);
}
}
public RequestQueue getQueue() {
if (queue == null) {
queue = Volley.newRequestQueue(getApplicationContext());
return queue;
GoalKicker.com – Android™ Notes for Professionals 626
}
return queue;
}
}
Now, you can use the volley instance using the getInstance() method and add a new request in the queue using
InitApplication.getInstance().addToQueue(request);
A simple example to request JsonObject from server is
JsonObjectRequest myRequest = new JsonObjectRequest(Method.GET,
url, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d(TAG, response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "Error: " + error.getMessage());
}
});
myRequest.setRetryPolicy(new DefaultRetryPolicy(
MY_SOCKET_TIMEOUT_MS,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
To handle Volley timeouts you need to use a RetryPolicy. A retry policy is used in case a request cannot be
completed due to network failure or some other cases.
Volley provides an easy way to implement your RetryPolicy for your requests. By default, Volley sets all socket and
connection timeouts to 5 seconds for all requests. RetryPolicy is an interface where you need to implement your
logic of how you want to retry a particular request when a timeout occurs.
The constructor takes the following three parameters:
initialTimeoutMs - Specifies the socket timeout in milliseconds for every retry attempt.
maxNumRetries - The number of times retry is attempted.
backoffMultiplier - A multiplier which is used to determine exponential time set to socket for every retry
attempt.
Section 98.2: Basic StringRequest using GET method
final TextView mTextView = (TextView) findViewById(R.id.text);
...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
GoalKicker.com – Android™ Notes for Professionals 627
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
Section 98.3: Adding custom design time attributes to
NetworkImageView
There are several additional attributes that the Volley NetworkImageView adds to the standard ImageView. However,
these attributes can only be set in code. The following is an example of how to make an extension class that will
pick up the attributes from your XML layout file and apply them to the NetworkImageView instance for you.
In your ~/res/xml directory, add a file named attrx.xml:
<resources>
<declare-styleable name="MoreNetworkImageView">
<attr name="defaultImageResId" format="reference"/>
<attr name="errorImageResId" format="reference"/>
</declare-styleable>
</resources>
Add a new class file to your project:
package my.namespace;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import com.android.volley.toolbox.NetworkImageView;
public class MoreNetworkImageView extends NetworkImageView {
public MoreNetworkImageView(@NonNull final Context context) {
super(context);
}
public MoreNetworkImageView(@NonNull final Context context, @NonNull final AttributeSet attrs)
{
this(context, attrs, 0);
}
public MoreNetworkImageView(@NonNull final Context context, @NonNull final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
final TypedArray attributes = context.obtainStyledAttributes(attrs,
R.styleable.MoreNetworkImageView, defStyle, 0);
// load defaultImageResId from XML
int defaultImageResId =
GoalKicker.com – Android™ Notes for Professionals 628
attributes.getResourceId(R.styleable.MoreNetworkImageView_defaultImageResId, 0);
if (defaultImageResId > 0) {
setDefaultImageResId(defaultImageResId);
}
// load errorImageResId from XML
int errorImageResId =
attributes.getResourceId(R.styleable.MoreNetworkImageView_errorImageResId, 0);
if (errorImageResId > 0) {
setErrorImageResId(errorImageResId);
}
}
}
An example layout file showing the use of the custom attributes:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="fill_parent">
<my.namespace.MoreNetworkImageView
android:layout_width="64dp"
android:layout_height="64dp"
app:errorImageResId="@drawable/error_img"
app:defaultImageResId="@drawable/default_img"
tools:defaultImageResId="@drawable/editor_only_default_img"/>
<!--
Note: The "tools:" prefix does NOT work for custom attributes in Android Studio 2.1 and
older at least, so in this example the defaultImageResId would show "default_img" in the
editor, not the "editor_only_default_img" drawable even though it should if it was
supported as an editor-only override correctly like standard Android properties.
-->
</android.support.v7.widget.CardView>
Section 98.4: Adding custom headers to your requests [e.g.
for basic auth]
If you need to add custom headers to your volley requests, you can't do this after initialisation, as the headers are
saved in a private variable.
Instead, you need to override the getHeaders() method of Request.class as such:
new JsonObjectRequest(REQUEST_METHOD, REQUEST_URL, REQUEST_BODY, RESP_LISTENER, ERR_LISTENER) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> customHeaders = new Hashmap<>();
customHeaders.put("KEY_0", "VALUE_0");
...
customHeaders.put("KEY_N", "VALUE_N");
return customHeaders;
}
};
GoalKicker.com – Android™ Notes for Professionals 629
Explanation of the parameters:
REQUEST_METHOD - Either of the Request.Method.* constants.
REQUEST_URL - The full URL to send your request to.
REQUEST_BODY - A JSONObject containing the POST-Body to be sent (or null).
RESP_LISTENER - A Response.Listener<?> object, whose onResponse(T data) method is called upon
successful completion.
ERR_LISTENER - A Response.ErrorListener object, whose onErrorResponse(VolleyError e) method is
called upon a unsuccessful request.
If you want to build a custom request, you can add the headers in it as well:
public class MyCustomRequest extends Request {
...
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> customHeaders = new Hashmap<>();
customHeaders.put("KEY_0", "VALUE_0");
...
customHeaders.put("KEY_N", "VALUE_N");
return customHeaders;
}
...
}
Section 98.5: Remote server authentication using
StringRequest through POST method
For the sake of this example, let us assume that we have a server for handling the POST requests that we will be
making from our Android app:
// User input data.
String email = "my@email.com";
String password = "123";
// Our server URL for handling POST requests.
String URL = "http://my.server.com/login.php";
// When we create a StringRequest (or a JSONRequest) for sending
// data with Volley, we specify the Request Method as POST, and
// the URL that will be receiving our data.
StringRequest stringRequest =
new StringRequest(Request.Method.POST, URL,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// At this point, Volley has sent the data to your URL
// and has a response back from it. I'm going to assume
// that the server sends an "OK" string.
if (response.equals("OK")) {
// Do login stuff.
} else {
// So the server didn't return an "OK" response.
// Depending on what you did to handle errors on your
// server, you can decide what action to take here.
}
GoalKicker.com – Android™ Notes for Professionals 630
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// This is when errors related to Volley happen.
// It's up to you what to do if that should happen, but
// it's usually not a good idea to be too clear as to
// what happened here to your users.
}
}) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
// Here is where we tell Volley what it should send in
// our POST request. For this example, we want to send
// both the email and the password.
// We will need key ids for our data, so our server can know
// what is what.
String key_email = "email";
String key_password = "password";
Map<String, String> map = new HashMap<String, String>();
// map.put(key, value);
map.put(key_email, email);
map.put(key_password, password);
return map;
}
};
// This is a policy that we need to specify to tell Volley, what
// to do if it gets a timeout, how many times to retry, etc.
stringRequest.setRetryPolicy(new RetryPolicy() {
@Override
public int getCurrentTimeout() {
// Here goes the timeout.
// The number is in milliseconds, 5000 is usually enough,
// but you can up or low that number to fit your needs.
return 50000;
}
@Override
public int getCurrentRetryCount() {
// The maximum number of attempts.
// Again, the number can be anything you need.
return 50000;
}
@Override
public void retry(VolleyError error) throws VolleyError {
// Here you could check if the retry count has gotten
// to the maximum number, and if so, send a VolleyError
// message or similar. For the sake of the example, I'll
// show a Toast.
Toast.makeText(getContext(), error.toString(), Toast.LENGTH_LONG).show();
}
});
// And finally, we create a Volley Queue. For this example, I'm using
// getContext(), because I was working with a Fragment. But context could
// be "this", "getContext()", etc.
RequestQueue requestQueue = Volley.newRequestQueue(getContext());
requestQueue.add(stringRequest);
GoalKicker.com – Android™ Notes for Professionals 631
} else {
// If, for example, the user inputs an email that is not currently
// on your remote DB, here's where we can inform the user.
Toast.makeText(getContext(), "Wrong email", Toast.LENGTH_LONG).show();
}
Section 98.6: Cancel a request
// assume a Request and RequestQueue have already been initialized somewhere above
public static final String TAG = "SomeTag";
// Set the tag on the request.
request.setTag(TAG);
// Add the request to the RequestQueue.
mRequestQueue.add(request);
// To cancel this specific request
request.cancel();
// ... then, in some future life cycle event, for example in onStop()
// To cancel all requests with the specified tag in RequestQueue
mRequestQueue.cancelAll(TAG);
Section 98.7: Request JSON
final TextView mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
ImageView mImageView;
String url = "http://ip.jsontest.com/";
final JsonObjectRequest jsObjRequest = new JsonObjectRequest
(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
mTxtDisplay.setText("Response: " + response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// ...
}
});
requestQueue.add(jsObjRequest);
Section 98.8: Use JSONArray as request body
The default requests integrated in volley don't allow to pass a JSONArray as request body in a POST request. Instead,
you can only pass a JSON object as a parameter.
However, instead of passing a JSON object as a parameter to the request constructor, you need to override the
getBody() method of the Request.class. You should pass null as third parameter as well:
JSONArray requestBody = new JSONArray();
new JsonObjectRequest(Request.Method.POST, REQUEST_URL, null, RESP_LISTENER, ERR_LISTENER) {
@Override
GoalKicker.com – Android™ Notes for Professionals 632
public byte[] getBody() {
try {
return requestBody.toString().getBytes(PROTOCOL_CHARSET);
} catch (UnsupportedEncodingException uee) {
// error handling
return null;
}
}
};
Explanation of the parameters:
REQUEST_URL - The full URL to send your request to.
RESP_LISTENER - A Response.Listener<?> object, whose onResponse(T data) method is called upon
successful completion.
ERR_LISTENER - A Response.ErrorListener object, whose onErrorResponse(VolleyError e) method is
called upon an unsuccessful request.
Section 98.9: Boolean variable response from server with json
request in volley
you can custom class below one
private final String PROTOCOL_CONTENT_TYPE = String.format("application/json; charset=%s",
PROTOCOL_CHARSET);
public BooleanRequest(int method, String url, String requestBody, Response.Listener<Boolean>
listener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
this.mErrorListener = errorListener;
this.mRequestBody = requestBody;
}
@Override
protected Response<Boolean> parseNetworkResponse(NetworkResponse response) {
Boolean parsed;
try {
parsed = Boolean.valueOf(new String(response.data,
HttpHeaderParser.parseCharset(response.headers)));
} catch (UnsupportedEncodingException e) {
parsed = Boolean.valueOf(new String(response.data));
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
@Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
return super.parseNetworkError(volleyError);
}
@Override
protected void deliverResponse(Boolean response) {
mListener.onResponse(response);
}
@Override
public void deliverError(VolleyError error) {
mErrorListener.onErrorResponse(error);
GoalKicker.com – Android™ Notes for Professionals 633
}
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
@Override
public byte[] getBody() throws AuthFailureError {
try {
return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (UnsupportedEncodingException uee) {
VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
mRequestBody, PROTOCOL_CHARSET);
return null;
}
}
}
use this with your activity
try {
JSONObject jsonBody;
jsonBody = new JSONObject();
jsonBody.put("Title", "Android Demo");
jsonBody.put("Author", "BNK");
jsonBody.put("Date", "2015/08/28");
String requestBody = jsonBody.toString();
BooleanRequest booleanRequest = new BooleanRequest(0, url, requestBody, new
Response.Listener<Boolean>() {
@Override
public void onResponse(Boolean response) {
Toast.makeText(mContext, String.valueOf(response), Toast.LENGTH_SHORT).show();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(mContext, error.toString(), Toast.LENGTH_SHORT).show();
}
});
// Add the request to the RequestQueue.
queue.add(booleanRequest);
} catch (JSONException e) {
e.printStackTrace();
}
Section 98.10: Helper Class for Handling Volley Errors
public class VolleyErrorHelper {
/**
* Returns appropriate message which is to be displayed to the user
* against the specified error object.
*
* @param error
* @param context
* @return
*/
public static String getMessage (Object error , Context context){
if(error instanceof TimeoutError){
GoalKicker.com – Android™ Notes for Professionals 634
return context.getResources().getString(R.string.timeout);
}else if (isServerProblem(error)){
return handleServerError(error ,context);
}else if(isNetworkProblem(error)){
return context.getResources().getString(R.string.nointernet);
}
return context.getResources().getString(R.string.generic_error);
}
private static String handleServerError(Object error, Context context) {
VolleyError er = (VolleyError)error;
NetworkResponse response = er.networkResponse;
if(response != null){
switch (response.statusCode){
case 404:
case 422:
case 401:
try {
// server might return error like this { "error": "Some error occurred"
}
// Use "Gson" to parse the result
HashMap<String, String> result = new Gson().fromJson(new
String(response.data),
new TypeToken<Map<String, String>>() {
}.getType());
if (result != null && result.containsKey("error")) {
return result.get("error");
}
} catch (Exception e) {
e.printStackTrace();
}
// invalid request
return ((VolleyError) error).getMessage();
default:
return context.getResources().getString(R.string.timeout);
}
}
return context.getResources().getString(R.string.generic_error);
}
private static boolean isServerProblem(Object error) {
return (error instanceof ServerError || error instanceof AuthFailureError);
}
private static boolean isNetworkProblem (Object error){
return (error instanceof NetworkError || error instanceof NoConnectionError);
}
GoalKicker.com – Android™ Notes for Professionals 635
Chapter 99: Date and Time Pickers
Section 99.1: Date Picker Dialog
It is a dialog which prompts user to select date using DatePicker. The dialog requires context, initial year, month
and day to show the dialog with starting date. When the user selects the date it callbacks via
DatePickerDialog.OnDateSetListener.
public void showDatePicker(Context context,int initialYear, int initialMonth, int initialDay) {
DatePickerDialog datePickerDialog = new DatePickerDialog(context,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker datepicker,int year ,int month, int day) {
//this condition is necessary to work properly on all android versions
if(view.isShown()){
//You now have the selected year, month and day
}
}
}, initialYear, initialMonth , initialDay);
//Call show() to simply show the dialog
datePickerDialog.show();
}
Please note that month is a int starting from 0 for January to 11 for December
Section 99.2: Material DatePicker
add below dependencies to build.gradle file in dependency section. (this is an unOfficial library for date picker)
compile 'com.wdullaer:materialdatetimepicker:2.3.0'
Now we have to open DatePicker on Button click event.
So create one Button on xml file like below.
<Button
android:id="@+id/dialog_bt_date"
android:layout_below="@+id/resetButton"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:textColor="#FF000000"
android:gravity="center"
android:text="DATE"/>
and in MainActivity use this way.
public class MainActivity extends AppCompatActivity implements DatePickerDialog.OnDateSetListener{
Button button;
Calendar calendar ;
DatePickerDialog datePickerDialog ;
int Year, Month, Day ;
GoalKicker.com – Android™ Notes for Professionals 636
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
calendar = Calendar.getInstance();
Year = calendar.get(Calendar.YEAR) ;
Month = calendar.get(Calendar.MONTH);
Day = calendar.get(Calendar.DAY_OF_MONTH);
Button dialog_bt_date = (Button)findViewById(R.id.dialog_bt_date);
dialog_bt_date.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
datePickerDialog = DatePickerDialog.newInstance(MainActivity.this, Year, Month,
Day);
datePickerDialog.setThemeDark(false);
datePickerDialog.showYearPickerFirst(false);
datePickerDialog.setAccentColor(Color.parseColor("#0072BA"));
datePickerDialog.setTitle("Select Date From DatePickerDialog");
datePickerDialog.show(getFragmentManager(), "DatePickerDialog");
}
});
}
@Override
public void onDateSet(DatePickerDialog view, int Year, int Month, int Day) {
String date = "Selected Date : " + Day + "-" + Month + "-" + Year;
Toast.makeText(MainActivity.this, date, Toast.LENGTH_LONG).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.abc_main_menu, menu);
return true;
}
}
Output :
GoalKicker.com – Android™ Notes for Professionals 637
GoalKicker.com – Android™ Notes for Professionals 638
Chapter 100: Localized Date/Time in
Android
Section 100.1: Custom localized date format with
DateUtils.formatDateTime()
DateUtils.formatDateTime() allows you to supply a time, and based on the flags you provide, it creates a localized
datetime string. The flags allow you to specify whether to include specific elements (like the weekday).
Date date = new Date(); String localizedDate = DateUtils.formatDateTime(context, date.getTime(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY);
formatDateTime() automatically takes care about proper date formats.
Section 100.2: Standard date/time formatting in Android
Format a date:
Date date = new Date(); DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); String localizedDate =
df.format(date)
Format a date and time. Date is in short format, time is in long format:
Date date = new Date(); DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG);
String localizedDate = df.format(date)
Section 100.3: Fully customized date/time
Date date = new Date(); df = new SimpleDateFormat("HH:mm", Locale.US); String localizedDate = df.format(date)
Commonly used patterns:
HH: hour (0-23)
hh: hour (1-12)
a: AM/PM marker
mm: minute (0-59)
ss: second
dd: day in month (1-31)
MM: month
yyyy: year
GoalKicker.com – Android™ Notes for Professionals 639
Chapter 101: Time Utils
Section 101.1: To check within a period
This example will help to verify the given time is within a period or not.
To check the time is today, We can use DateUtils class
boolean isToday = DateUtils.isToday(timeInMillis);
To check the time is within a week,
private static boolean isWithinWeek(final long millis) {
return System.currentTimeMillis() - millis <= (DateUtils.WEEK_IN_MILLIS -
DateUtils.DAY_IN_MILLIS);
}
To check the time is within a year,
private static boolean isWithinYear(final long millis) {
return System.currentTimeMillis() - millis <= DateUtils.YEAR_IN_MILLIS;
}
To check the time is within a number day of day including today,
public static boolean isWithinDay(long timeInMillis, int day) {
long diff = System.currentTimeMillis() - timeInMillis;
float dayCount = (float) (diff / DateUtils.DAY_IN_MILLIS);
return dayCount < day;
}
Note : DateUtils is android.text.format.DateUtils
Section 101.2: Convert Date Format into Milliseconds
To Convert you date in dd/MM/yyyy format into milliseconds you call this function with data as String
public long getMilliFromDate(String dateFormat) {
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
try {
date = formatter.parse(dateFormat);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("Today is " + date);
return date.getTime();
}
This method converts milliseconds to Time-stamp Format date :
public String getTimeStamp(long timeinMillies) {
String date = null;
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // modify format
GoalKicker.com – Android™ Notes for Professionals 640
date = formatter.format(new Date(timeinMillies));
System.out.println("Today is " + date);
return date;
}
This Method will convert given specific day,month and year into milliseconds. It will be very help when using
Timpicker or Datepicker
public static long getTimeInMillis(int day, int month, int year) {
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day);
return calendar.getTimeInMillis();
}
It will return milliseconds from date
public static String getNormalDate(long timeInMillies) {
String date = null;
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
date = formatter.format(timeInMillies);
System.out.println("Today is " + date);
return date;
}
It will return current date
public static String getCurrentDate() {
Calendar c = Calendar.getInstance();
System.out.println("Current time => " + c.getTime());
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
String formattedDate = df.format(c.getTime());
return formattedDate;
}
Note : Java Provides numbers of date format support Date Pattern
Section 101.3: GetCurrentRealTime
This calculate current device time and add/subtract difference between real and device time
public static Calendar getCurrentRealTime() {
long bootTime = networkTime - SystemClock.elapsedRealtime();
Calendar calInstance = Calendar.getInstance();
calInstance.setTimeZone(getUTCTimeZone());
long currentDeviceTime = bootTime + SystemClock.elapsedRealtime();
calInstance.setTimeInMillis(currentDeviceTime);
return calInstance;
}
get UTC based timezone.
public static TimeZone getUTCTimeZone() {
return TimeZone.getTimeZone("GMT");
}
GoalKicker.com – Android™ Notes for Professionals 641
Chapter 102: In-app Billing
Section 102.1: Consumable In-app Purchases
Consumable Managed Products are products that can be bought multiple times such as in-game currency, game
lives, power-ups, etc.
In this example, we are going to implement 4 different consumable managed products "item1", "item2",
"item3", "item4".
Steps in summary:
1. Add the In-app Billing library to your project (AIDL File).
2. Add the required permission in AndroidManifest.xml file.
3. Deploy a signed apk to Google Developers Console.
4. Define your products.
5. Implement the code.
6. Test In-app Billing (optional).
Step 1:
First of all, we will need to add the AIDL file to your project as clearly explained in Google Documentation here.
IInAppBillingService.aidl is an Android Interface Definition Language (AIDL) file that defines the interface to the
In-app Billing Version 3 service. You will use this interface to make billing requests by invoking IPC method calls.
Step 2:
After adding the AIDL file, add BILLING permission in AndroidManifest.xml:
<!-- Required permission for implementing In-app Billing -->
<uses-permission android:name="com.android.vending.BILLING" />
Step 3:
Generate a signed apk, and upload it to Google Developers Console. This is required so that we can start defining
our in-app products there.
Step 4:
Define all your products with different productID, and set a price to each one of them. There are 2 types of
products (Managed Products and Subscriptions). As we already said, we are going to implement 4 different
consumable managed products "item1", "item2", "item3", "item4".
Step 5:
After doing all the steps above, you are now ready to start implementing the code itself in your own activity.
MainActivity:
public class MainActivity extends Activity {
IInAppBillingService inAppBillingService;
ServiceConnection serviceConnection;
GoalKicker.com – Android™ Notes for Professionals 642
// productID for each item. You should define them in the Google Developers Console.
final String item1 = "item1";
final String item2 = "item2";
final String item3 = "item3";
final String item4 = "item4";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Instantiate the views according to your layout file.
final Button buy1 = (Button) findViewById(R.id.buy1);
final Button buy2 = (Button) findViewById(R.id.buy2);
final Button buy3 = (Button) findViewById(R.id.buy3);
final Button buy4 = (Button) findViewById(R.id.buy4);
// setOnClickListener() for each button.
// buyItem() here is the method that we will implement to launch the PurchaseFlow.
buy1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
buyItem(item1);
}
});
buy2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
buyItem(item2);
}
});
buy3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
buyItem(item3);
}
});
buy4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
buyItem(item4);
}
});
// Attach the service connection.
serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
inAppBillingService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
inAppBillingService = IInAppBillingService.Stub.asInterface(service);
}
};
// Bind the service.
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
GoalKicker.com – Android™ Notes for Professionals 643
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE);
// Get the price of each product, and set the price as text to
// each button so that the user knows the price of each item.
if (inAppBillingService != null) {
// Attention: You need to create a new thread here because
// getSkuDetails() triggers a network request, which can
// cause lag to your app if it was called from the main thread.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ArrayList<String> skuList = new ArrayList<>();
skuList.add(item1);
skuList.add(item2);
skuList.add(item3);
skuList.add(item4);
Bundle querySkus = new Bundle();
querySkus.putStringArrayList("ITEM_ID_LIST", skuList);
try {
Bundle skuDetails = inAppBillingService.getSkuDetails(3, getPackageName(),
"inapp", querySkus);
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
ArrayList<String> responseList =
skuDetails.getStringArrayList("DETAILS_LIST");
for (String thisResponse : responseList) {
JSONObject object = new JSONObject(thisResponse);
String sku = object.getString("productId");
String price = object.getString("price");
switch (sku) {
case item1:
buy1.setText(price);
break;
case item2:
buy2.setText(price);
break;
case item3:
buy3.setText(price);
break;
case item4:
buy4.setText(price);
break;
}
}
}
} catch (RemoteException | JSONException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
// Launch the PurchaseFlow passing the productID of the item the user wants to buy as a
parameter.
private void buyItem(String productID) {
GoalKicker.com – Android™ Notes for Professionals 644
if (inAppBillingService != null) {
try {
Bundle buyIntentBundle = inAppBillingService.getBuyIntent(3, getPackageName(),
productID, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(), 1003, new Intent(), 0,
0, 0);
} catch (RemoteException | IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
}
// Unbind the service in onDestroy(). If you don’t unbind, the open
// service connection could cause your device’s performance to degrade.
@Override
public void onDestroy() {
super.onDestroy();
if (inAppBillingService != null) {
unbindService(serviceConnection);
}
}
// Check here if the in-app purchase was successful or not. If it was successful,
// then consume the product, and let the app make the required changes.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1003 && resultCode == RESULT_OK) {
final String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
// Attention: You need to create a new thread here because
// consumePurchase() triggers a network request, which can
// cause lag to your app if it was called from the main thread.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
JSONObject jo = new JSONObject(purchaseData);
// Get the productID of the purchased item.
String sku = jo.getString("productId");
String productName = null;
// increaseCoins() here is a method used as an example in a game to
// increase the in-game currency if the purchase was successful.
// You should implement your own code here, and let the app apply
// the required changes after the purchase was successful.
switch (sku) {
case item1:
productName = "Item 1";
increaseCoins(2000);
break;
case item2:
productName = "Item 2";
increaseCoins(8000);
break;
case item3:
productName = "Item 3";
increaseCoins(18000);
break;
GoalKicker.com – Android™ Notes for Professionals 645
case item4:
productName = "Item 4";
increaseCoins(30000);
break;
}
// Consume the purchase so that the user is able to purchase the same
product again.
inAppBillingService.consumePurchase(3, getPackageName(),
jo.getString("purchaseToken"));
Toast.makeText(MainActivity.this, productName + " is successfully
purchased. Excellent choice, master!", Toast.LENGTH_LONG).show();
} catch (JSONException | RemoteException e) {
Toast.makeText(MainActivity.this, "Failed to parse purchase data.",
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
});
thread.start();
}
}
}
Step 6:
After implementing the code, you can test it by deploying your apk to beta/alpha channel, and let other users test
the code for you. However, real in-app purchases can't be made while in testing mode. You have to publish your
app/game first to Play Store so that all the products are fully activated.
More info on testing In-app Billing can be found here.
Section 102.2: (Third party) In-App v3 Library
Step 1: First of all follow these two steps to add in app functionality :
1. Add the library using :
repositories {
mavenCentral()
}
dependencies {
compile 'com.anjlab.android.iab.v3:library:1.0.+'
}
2. Add permission in manifest file.
<uses-permission android:name="com.android.vending.BILLING" />
Step 2: Initialise your billing processor:
BillingProcessor bp = new BillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE",
this);
and implement Billing Handler : BillingProcessor.IBillingHandler which contains 4 methods : a. onBillingInitialized();
b. onProductPurchased(String productId, TransactionDetails details) : This is where you need to handle actions to
be performed after successful purchase c. onBillingError(int errorCode, Throwable error) : Handle any error
occurred during purchase process d. onPurchaseHistoryRestored() : For restoring in app purchases
GoalKicker.com – Android™ Notes for Professionals 646
Step 3: How to purchase a product.
To purchase a managed product :
bp.purchase(YOUR_ACTIVITY, "YOUR PRODUCT ID FROM GOOGLE PLAY CONSOLE HERE");
And to Purchase a subscription :
bp.subscribe(YOUR_ACTIVITY, "YOUR SUBSCRIPTION ID FROM GOOGLE PLAY CONSOLE HERE");
Step 4 : Consuming a product.
To consume a product simply call consumePurchase method.
bp.consumePurchase("YOUR PRODUCT ID FROM GOOGLE PLAY CONSOLE HERE");
For other methods related to in app visit github
GoalKicker.com – Android™ Notes for Professionals 647
Chapter 103: FloatingActionButton
Parameter Detail
android.support.design:elevation
Elevation value for the FAB. May be a reference to another resource,
in the form "@[+][package:]type/name" or a theme attribute in the
form "?[package:]type/name".
android.support.design:fabSize Size for the FAB.
android.support.design:rippleColor Ripple color for the FAB.
android.support.design:useCompatPadding Enable compat padding.
Floating action button is used for a special type of promoted action,it animates onto the screen as an expanding
piece of material, by default. The icon within it may be animated,also FAB may move differently than other UI
elements because of their relative importance. A floating action button represents the primary action in an
application which can simply trigger an action or navigate somewhere.
Section 103.1: How to add the FAB to the layout
To use a FloatingActionButton just add the dependency in the build.gradle file as described in the remarks
section.
Then add to the layout:
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/my_icon" />
An example:
Color
The background color of this view defaults to the your theme's colorAccent.
In the above image if the src only points to + icon (by default 24x24 dp),to get the background color of full circle you
can use app:backgroundTint="@color/your_colour"
If you wish to change the color in code you can use,
GoalKicker.com – Android™ Notes for Professionals 648
myFab.setBackgroundTintList(ColorStateList.valueOf(your color in int));
If you want to change FAB's color in pressed state use
mFab.setRippleColor(your color in int);
Positioning
It is recommended to place 16dp minimum from the edge on mobile,and 24dp minimum on tablet/desktop.
Note : Once you set an src excepting to cover the full area of FloatingActionButton make sure you have the right
size of that image to get the best result.
Default circle size is 56 x 56dp
Mini circle size : 40 x 40dp
If you only want to change only the Interior icon use a 24 x 24dp icon for default size
Section 103.2: Show and Hide FloatingActionButton on Swipe
To show and hide a FloatingActionButton with the default animation, just call the methods show() and hide(). It's
good practice to keep a FloatingActionButton in the Activity layout instead of putting it in a Fragment, this allows
the default animations to work when showing and hiding.
Here is an example with a ViewPager:
Three Tabs
Show FloatingActionButton for the first and third Tab
Hide the FloatingActionButton on the middle Tab
public class MainActivity extends AppCompatActivity {
FloatingActionButton fab;
ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fab = (FloatingActionButton) findViewById(R.id.fab);
viewPager = (ViewPager) findViewById(R.id.viewpager);
// ...... set up ViewPager ............
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
if (position == 0) {
fab.setImageResource(android.R.drawable.ic_dialog_email);
fab.show();
} else if (position == 2) {
GoalKicker.com – Android™ Notes for Professionals 649
fab.setImageResource(android.R.drawable.ic_dialog_map);
fab.show();
} else {
fab.hide();
}
}
@Override
public void onPageScrolled(int position, float positionOffset, int
positionOffsetPixels) {}
@Override
public void onPageScrollStateChanged(int state) {}
});
// Handle the FloatingActionButton click event:
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = viewPager.getCurrentItem();
if (position == 0) {
openSend();
} else if (position == 2) {
openMap();
}
}
});
}
}
Result:
GoalKicker.com – Android™ Notes for Professionals 650
Section 103.3: Show and Hide FloatingActionButton on Scroll
Starting with the Support Library version 22.2.1, it's possible to show and hide a FloatingActionButton from scrolling
behavior using a FloatingActionButton.Behavior sublclass that takes advantage of the show() and hide()
methods.
Note that this only works with a CoordinatorLayout in conjunction with inner Views that support Nested Scrolling,
such as RecyclerView and NestedScrollView.
This ScrollAwareFABBehavior class comes from the Android Guides on Codepath (cc-wiki with attribution required)
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
super();
}
@Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final
FloatingActionButton child,
final View directTargetChild, final View target, final int
nestedScrollAxes) {
// Ensure we react to vertical scrolling
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
nestedScrollAxes);
}
@Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final
FloatingActionButton child,
final View target, final int dxConsumed, final int dyConsumed,
final int dxUnconsumed, final int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
// User scrolled down and the FAB is currently visible -> hide the FAB
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
// User scrolled up and the FAB is currently not visible -> show the FAB
child.show();
}
}
}
In the FloatingActionButton layout xml, specify the app:layout_behavior with the fully-qualified-class-name of
ScrollAwareFABBehavior:
app:layout_behavior="com.example.app.ScrollAwareFABBehavior"
For example with this layout:
<android.support.design.widget.CoordinatorLayout
android:id="@+id/main_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
GoalKicker.com – Android™ Notes for Professionals 651
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="6dp">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:elevation="0dp"
app:layout_scrollFlags="scroll|enterAlways"
/>
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
app:tabMode="fixed"
android:layout_below="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:elevation="0dp"
app:tabTextColor="#d3d3d3"
android:minHeight="?attr/actionBarSize"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_below="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:layout_behavior="com.example.app.ScrollAwareFABBehavior"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
Here is the result:
GoalKicker.com – Android™ Notes for Professionals 652
Section 103.4: Setting behaviour of FloatingActionButton
You can set the behavior of the FAB in XML.
For example:
<android.support.design.widget.FloatingActionButton
app:layout_behavior=".MyBehavior" />
Or you can set programmatically using:
CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
p.setBehavior(xxxx);
fab.setLayoutParams(p);
GoalKicker.com – Android™ Notes for Professionals 653
Chapter 104: Touch Events
Section 104.1: How to vary between child and parent view
group touch events
1. The onTouchEvents() for nested view groups can be managed by the boolean onInterceptTouchEvent.
The default value for the OnInterceptTouchEvent is false.
The parent's onTouchEvent is received before the child's. If the OnInterceptTouchEvent returns false, it sends the
motion event down the chain to the child's OnTouchEvent handler. If it returns true the parent's will handle the
touch event.
However there may be instances when we want some child elements to manage OnTouchEvents and some to be
managed by the parent view (or possibly the parent of the parent).
This can be managed in more than one way.
2. One way a child element can be protected from the parent's OnInterceptTouchEvent is by implementing the
requestDisallowInterceptTouchEvent.
public void requestDisallowInterceptTouchEvent (boolean disallowIntercept)
This prevents any of the parent views from managing the OnTouchEvent for this element, if the element has event
handlers enabled.
If the OnInterceptTouchEvent is false, the child element's OnTouchEvent will be evaluated. If you have a methods
within the child elements handling the various touch events, any related event handlers that are disabled will return
the OnTouchEvent to the parent.
This answer:
A visualisation of how the propagation of touch events passes through:
parent -> child|parent -> child|parent -> child views.
GoalKicker.com – Android™ Notes for Professionals 654
Courtesy from here
4. Another way is returning varying values from the OnInterceptTouchEvent for the parent.
This example taken from Managing Touch Events in a ViewGroup and demonstrates how to intercept the child's
OnTouchEvent when the user is scrolling.
4a.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onTouchEvent will be called and we do the actual
* scrolling there.
*/
final int action = MotionEventCompat.getActionMasked(ev);
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the scroll.
mIsScrolling = false;
return false; // Do not intercept touch event, let the child handle it
GoalKicker.com – Android™ Notes for Professionals 655
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
if (mIsScrolling) {
// We're currently scrolling, so yes, intercept the
// touch event!
return true;
}
// If the user has dragged her finger horizontally more than
// the touch slop, start the scroll
// left as an exercise for the reader
final int xDiff = calculateDistanceX(ev);
// Touch slop should be calculated using ViewConfiguration
// constants.
if (xDiff > mTouchSlop) {
// Start scrolling!
mIsScrolling = true;
return true;
}
break;
}
...
}
// In general, we don't want to intercept touch events. They should be
// handled by the child view.
return false;
}
This is some code from the same link showing how to create the parameters of the rectangle around your element:
4b.
// The hit rectangle for the ImageButton
myButton.getHitRect(delegateArea);
// Extend the touch area of the ImageButton beyond its bounds
// on the right and bottom.
delegateArea.right += 100;
delegateArea.bottom += 100;
// Instantiate a TouchDelegate.
// "delegateArea" is the bounds in local coordinates of
// the containing view to be mapped to the delegate view.
// "myButton" is the child view that should receive motion
// events.
TouchDelegate touchDelegate = new TouchDelegate(delegateArea, myButton);
// Sets the TouchDelegate on the parent view, such that touches
// within the touch delegate bounds are routed to the child.
if (View.class.isInstance(myButton.getParent())) {
((View) myButton.getParent()).setTouchDelegate(touchDelegate);
}
GoalKicker.com – Android™ Notes for Professionals 656
Chapter 105: Handling touch and motion
events
Listener Details
onTouchListener Handles single touches for buttons, surfaces and more
onTouchEvent A listener that can be found in surfaces(e.g. SurfaceView). Does not need to be set like other
listeners(e,g. onTouchListener)
onLongTouch Similar to onTouch, but listens for long presses in buttons, surfaces and more.
A summary of some of the basic touch/motion-handling systems in the Android API.
Section 105.1: Buttons
Touch events related to a Button can be checked as follows:
public class ExampleClass extends Activity implements View.OnClickListener,
View.OnLongClickListener{
public Button onLong, onClick;
@Override
public void onCreate(Bundle sis){
super.onCreate(sis);
setContentView(R.layout.layout);
onLong = (Button) findViewById(R.id.onLong);
onClick = (Button) findViewById(R.id.onClick);
// The buttons are created. Now we need to tell the system that
// these buttons have a listener to check for touch events.
// "this" refers to this class, as it contains the appropriate event listeners.
onLong.setOnLongClickListener(this);
onClick.setOnClickListener(this);
[OR]
onClick.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
// Take action. This listener is only designed for one button.
// This means, no other input will come here.
// This makes a switch statement unnecessary here.
}
});
onLong.setOnLongClickListener(new View.OnLongClickListener(){
@Override
public boolean onLongClick(View v){
// See comment in onClick.setOnClickListener().
}
});
}
@Override
public void onClick(View v) {
// If you have several buttons to handle, use a switch to handle them.
switch(v.getId()){
case R.id.onClick:
// Take action.
break;
}
GoalKicker.com – Android™ Notes for Professionals 657
}
@Override
public boolean onLongClick(View v) {
// If you have several buttons to handle, use a switch to handle them.
switch(v.getId()){
case R.id.onLong:
// Take action.
break;
}
return false;
}
}
Section 105.2: Surface
Touch event handler for surfaces (e.g. SurfaceView, GLSurfaceView, and others):
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
public class ExampleClass extends Activity implements View.OnTouchListener{
@Override
public void onCreate(Bundle sis){
super.onCreate(sis);
CustomSurfaceView csv = new CustomSurfaceView(this);
csv.setOnTouchListener(this);
setContentView(csv);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
// Add a switch (see buttons example) if you handle multiple views
// here you can see (using MotionEvent event) to see what touch event
// is being taken. Is the pointer touching or lifted? Is it moving?
return false;
}
}
Or alternatively (in the surface):
public class CustomSurfaceView extends SurfaceView {
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
// Handle touch events here. When doing this, you do not need to call a listener.
// Please note that this listener only applies to the surface it is placed in
// (in this case, CustomSurfaceView), which means that anything else which is
// pressed outside the SurfaceView is handled by the parts of your app that
// have a listener in that area.
return true;
}
}
GoalKicker.com – Android™ Notes for Professionals 658
Section 105.3: Handling multitouch in a surface
public class CustomSurfaceView extends SurfaceView {
@Override
public boolean onTouchEvent(MotionEvent e) {
super.onTouchEvent(e);
if(e.getPointerCount() > 2){
return false; // If we want to limit the amount of pointers, we return false
// which disallows the pointer. It will not be reacted on either, for
// any future touch events until it has been lifted and repressed.
}
// What can you do here? Check if the amount of pointers are [x] and take action,
// if a pointer leaves, a new enters, or the [x] pointers are moved.
// Some examples as to handling etc. touch/motion events.
switch (MotionEventCompat.getActionMasked(e)) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// One or more pointers touch the screen.
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
// One or more pointers stop touching the screen.
break;
case MotionEvent.ACTION_MOVE:
// One or more pointers move.
if(e.getPointerCount() == 2){
move();
}else if(e.getPointerCount() == 1){
paint();
}else{
zoom();
}
break;
}
return true; // Allow repeated action.
}
}
GoalKicker.com – Android™ Notes for Professionals 659
Chapter 106: Detect Shake Event in
Android
Section 106.1: Shake Detector in Android Example
public class ShakeDetector implements SensorEventListener {
private static final float SHAKE_THRESHOLD_GRAVITY = 2.7F;
private static final int SHAKE_SLOP_TIME_MS = 500;
private static final int SHAKE_COUNT_RESET_TIME_MS = 3000;
private OnShakeListener mListener;
private long mShakeTimestamp;
private int mShakeCount;
public void setOnShakeListener(OnShakeListener listener) {
this.mListener = listener;
}
public interface OnShakeListener {
public void onShake(int count);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// ignore
}
@Override
public void onSensorChanged(SensorEvent event) {
if (mListener != null) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float gX = x / SensorManager.GRAVITY_EARTH;
float gY = y / SensorManager.GRAVITY_EARTH;
float gZ = z / SensorManager.GRAVITY_EARTH;
// gForce will be close to 1 when there is no movement.
float gForce = FloatMath.sqrt(gX * gX + gY * gY + gZ * gZ);
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
final long now = System.currentTimeMillis();
// ignore shake events too close to each other (500ms)
if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
return;
}
// reset the shake count after 3 seconds of no shakes
if (mShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
mShakeCount = 0;
}
mShakeTimestamp = now;
mShakeCount++;
GoalKicker.com – Android™ Notes for Professionals 660
mListener.onShake(mShakeCount);
}
}
}
}
Section 106.2: Using Seismic shake detection
Seismic is an Android device shake detection library by Square. To use it just start listening to the shake events
emitted by it.
@Override
protected void onCreate(Bundle savedInstanceState) {
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
sd = new ShakeDetector(() -> { /* react to detected shake */ });
}
@Override
protected void onResume() {
sd.start(sm);
}
@Override
protected void onPause() {
sd.stop();
}
To define the a different acceleration threshold use sd.setSensitivity(sensitivity) with a sensitivity of
SENSITIVITY_LIGHT, SENSITIVITY_MEDIUM, SENSITIVITY_HARD or any other reasonable integer value. The given
default values range from 11 to 15.
Installation
compile 'com.squareup:seismic:1.0.2'
GoalKicker.com – Android™ Notes for Professionals 661
Chapter 107: Hardware Button
Events/Intents (PTT, LWP, etc.)
Several android devices have custom buttons added by the manufacturer. This opens new possibilities for the
developer in handling those buttons especially when making Apps targeted for Hardware Devices.
This topic documents buttons which have intents attached to them which you can listen for via intent-receivers.
Section 107.1: Sonim Devices
Sonim devices have varying by model a lot of different custom buttons:
PTT_KEY
com.sonim.intent.action.PTT_KEY_DOWN
com.sonim.intent.action.PTT_KEY_UP
YELLOW_KEY
com.sonim.intent.action.YELLOW_KEY_DOWN
com.sonim.intent.action.YELLOW_KEY_UP
SOS_KEY
com.sonim.intent.action.SOS_KEY_DOWN
com.sonim.intent.action.SOS_KEY_UP
GREEN_KEY
com.sonim.intent.action.GREEN_KEY_DOWN
com.sonim.intent.action.GREEN_KEY_UP
Registering the buttons
To receive those intents you will have to assign the buttons to your app in the Phone-Settings. Sonim has a
possibilty to auto-register the buttons to the App when it is installed. In order to do that you will have to contact
them and get a package-specific key to include in your Manifest like this:
<meta-data
android:name="app_key_green_data"
android:value="your-key-here" />
Section 107.2: RugGear Devices
PTT Button
android.intent.action.PTT.down
android.intent.action.PTT.up
Confirmed on: RG730, RG740A
GoalKicker.com – Android™ Notes for Professionals 662
Chapter 108: GreenRobot EventBus
Thread Mode Description
ThreadMode.POSTING Will be called on the same thread that the event was posted on. This is the default mode.
ThreadMode.MAIN Will be called on the main UI thread.
ThreadMode.BACKGROUND
Will be called on a background thread. If the posting thread isn't the main thread it will be
used. If posted on the main thread EventBus has a single background thread that it will
use.
ThreadMode.ASYNC Will be called on its own thread.
Section 108.1: Passing a Simple Event
The first thing we need to do it add EventBus to our module's gradle file:
dependencies {
...
compile 'org.greenrobot:eventbus:3.0.0'
...
}
Now we need to create a model for our event. It can contain anything we want to pass along. For now we'll just
make an empty class.
public class DeviceConnectedEvent
{
}
Now we can add the code to our Activity that will register with EventBus and subscribe to the event.
public class MainActivity extends AppCompatActivity
{
private EventBus _eventBus;
@Override
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
_eventBus = EventBus.getDefault();
}
@Override
protected void onStart ()
{
super.onStart();
_eventBus.register(this);
}
@Override
protected void onStop ()
{
_eventBus.unregister(this);
super.onStop();
}
@Subscribe(threadMode = ThreadMode.MAIN)
GoalKicker.com – Android™ Notes for Professionals 663
public void onDeviceConnected (final DeviceConnectedEvent event)
{
// Process event and update UI
}
}
In this Activity we get an instance of EventBus in the onCreate() method. We register / unregister for events in
onStart() / onStop(). It's important to remember to unregister when your listener loses scope or you could leak
your Activity.
Finally we define the method that we want called with the event. The @Subscribe annotation tells EventBus which
methods it can look for to handle events. You have to have at least one methods annotated with @Subscribe to
register with EventBus or it will throw an exception. In the annotation we define the thread mode. This tells
EventBus which thread to call the method on. It is a very handy way of passing information from a background
thread to the UI thread! That's exactly what we're doing here. ThreadMode.MAIN means that this method will be
called on Android's main UI thread so it's safe to do any UI manipulations here that you need. The name of the
method doesn't matter. The only think, other that the @Subscribe annotation, that EventBus is looking for is the
type of the argument. As long as the type matches it will be called when an event is posted.
The last thing we need to do it to post an event. This code will be in our Service.
EventBus.getDefault().post(new DeviceConnectedEvent());
That's all there is to it! EventBus will take that DeviceConnectedEvent and look through its registered listeners, look
through the methods that they've subscribed and find the ones that take a DeviceConnectedEvent as an argument
and call them on the thread that they want to be called on.
Section 108.2: Receiving Events
For receiving events you need to register your class on the EventBus.
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
And then subscribe to the events.
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleEvent(ArbitraryEvent event) {
Toast.makeText(getActivity(), "Event type: "+event.getEventType(), Toast.LENGTH_SHORT).show();
}
Section 108.3: Sending Events
Sending events is as easy as creating the Event object and then posting it.
EventBus.getDefault().post(new ArbitraryEvent(ArbitraryEvent.TYPE_1));
GoalKicker.com – Android™ Notes for Professionals 664
Chapter 109: Otto Event Bus
Section 109.1: Passing an event
This example describes passing an event using the Otto Event Bus.
To use the Otto Event Bus in Android Studio you have to insert the following statement in your modules gradle file:
dependencies {
compile 'com.squareup:otto:1.3.8'
}
The event we'd like to pass is a simple Java object:
public class DatabaseContentChangedEvent {
public String message;
public DatabaseContentChangedEvent(String message) {
this.message = message;
}
}
We need a Bus to send events. This is typically a singleton:
import com.squareup.otto.Bus;
public final class BusProvider {
private static final Bus mBus = new Bus();
public static Bus getInstance() {
return mBus;
}
private BusProvider() {
}
}
To send an event we only need our BusProvider and it's post method. Here we send an event if the action of an
AsyncTask is completed:
public abstract class ContentChangingTask extends AsyncTask<Object, Void, Void> {
...
@Override
protected void onPostExecute(Void param) {
BusProvider.getInstance().post(
new DatabaseContentChangedEvent("Content changed")
);
}
}
Section 109.2: Receiving an event
To receive an event it is necessary to implement a method with the event type as parameter and annotate it using
@Subscribe. Furthermore you have to register/unregister the instance of your object at the BusProvider (see
GoalKicker.com – Android™ Notes for Professionals 665
example Sending an event):
public class MyFragment extends Fragment {
private final static String TAG = "MyFragment";
...
@Override
public void onResume() {
super.onResume();
BusProvider.getInstance().register(this);
}
@Override
public void onPause() {
super.onPause();
BusProvider.getInstance().unregister(this);
}
@Subscribe
public void onDatabaseContentChanged(DatabaseContentChangedEvent event) {
Log.i(TAG, "onDatabaseContentChanged: "+event.message);
}
}
Important: In order to receive that event an instance of the class has to exist. This is usually not the case when you
want to send a result from one activity to another activity. So check your use case for the event bus.
GoalKicker.com – Android™ Notes for Professionals 666
Chapter 110: Vibration
Section 110.1: Getting Started with Vibration
Grant Vibration Permission
before you start implement code, you have to add permission in android manifest :
<uses-permission android:name="android.permission.VIBRATE"/>
Import Vibration Library
import android.os.Vibrator;
Get instance of Vibrator from Context
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
Check device has vibrator
void boolean isHaveVibrate(){
if (vibrator.hasVibrator()) {
return true;
}
return false;
}
Section 110.2: Vibrate Indefinitely
using the vibrate(long[] pattern, int repeat)
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
// Start time delay
// Vibrate for 500 milliseconds
// Sleep for 1000 milliseconds
long[] pattern = {0, 500, 1000};
// 0 meaning is repeat indefinitely
vibrator.vibrate(pattern, 0);
Section 110.3: Vibration Patterns
You can create vibration patterns by passing in an array of longs, each of which represents a duration in
milliseconds. The first number is start time delay. Each array entry then alternates between vibrate, sleep, vibrate,
sleep, etc.
The following example demonstrates this pattern:
vibrate 100 milliseconds and sleep 1000 milliseconds
vibrate 200 milliseconds and sleep 2000 milliseconds
long[] pattern = {0, 100, 1000, 200, 2000};
GoalKicker.com – Android™ Notes for Professionals 667
To cause the pattern to repeat, pass in the index into the pattern array at which to start the repeat, or -1 to disable
repeating.
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(pattern, -1); // does not repeat
vibrator.vibrate(pattern, 0); // repeats forever
Section 110.4: Stop Vibrate
If you want stop vibrate please call :
vibrator.cancel();
Section 110.5: Vibrate for one time
using the vibrate(long milliseconds)
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(500);
GoalKicker.com – Android™ Notes for Professionals 668
Chapter 111: ContentProvider
Section 111.1: Implementing a basic content provider class
1) Create a Contract Class
A contract class defines constants that help applications work with the content URIs, column names, intent actions,
and other features of a content provider. Contract classes are not included automatically with a provider; the
provider's developer has to define them and then make them available to other developers.
A provider usually has a single authority, which serves as its Android-internal name. To avoid conflicts with other
providers, use a unique content authority. Because this recommendation is also true for Android package names,
you can define your provider authority as an extension of the name of the package containing the provider. For
example, if your Android package name is com.example.appname, you should give your provider the authority
com.example.appname.provider.
public class MyContract {
public static final String CONTENT_AUTHORITY = "com.example.myApp";
public static final String PATH_DATATABLE = "dataTable";
public static final String TABLE_NAME = "dataTable";
}
A content URI is a URI that identifies data in a provider. Content URIs include the symbolic name of the entire
provider (its authority) and a name that points to a table or file (a path). The optional id part points to an individual
row in a table. Every data access method of ContentProvider has a content URI as an argument; this allows you to
determine the table, row, or file to access. Define these in the contract class.
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_DATATABLE).build();
// define all columns of table and common functions required
2) Create the Helper Class
A helper class manages database creation and version management.
public class DatabaseHelper extends SQLiteOpenHelper {
// Increment the version when there is a change in the structure of database
public static final int DATABASE_VERSION = 1;
// The name of the database in the filesystem, you can choose this to be anything
public static final String DATABASE_NAME = "weather.db";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// Called when the database is created for the first time. This is where the
// creation of tables and the initial population of the tables should happen.
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
GoalKicker.com – Android™ Notes for Professionals 669
// Called when the database needs to be upgraded. The implementation
// should use this method to drop tables, add tables, or do anything else it
// needs to upgrade to the new schema version.
}
}
3) Create a class that extends ContentProvider class
public class MyProvider extends ContentProvider {
public DatabaseHelper dbHelper;
public static final UriMatcher matcher = buildUriMatcher();
public static final int DATA_TABLE = 100;
public static final int DATA_TABLE_DATE = 101;
A UriMatcher maps an authority and path to an integer value. The method match() returns a unique integer value
for a URI (it can be any arbitrary number, as long as it's unique). A switch statement chooses between querying the
entire table, and querying for a single record. Our UriMatcher returns 100 if the URI is the Content URI of Table and
101 if the URI points to a specific row within that table. You can use the # wildcard to match with any number and *
to match with any string.
public static UriMatcher buildUriMatcher() {
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(CONTENT_AUTHORITY, MyContract.PATH_DATATABLE, DATA_TABLE);
uriMatcher.addURI(CONTENT_AUTHORITY, MyContract.PATH_DATATABLE + "/#", DATA_TABLE_DATE);
return uriMatcher;
}
IMPORTANT: the ordering of addURI() calls matters! The UriMatcher will look in sequential order from first added
to last. Since wildcards like # and * are greedy, you will need to make sure that you have ordered your URIs
correctly. For example:
uriMatcher.addURI(CONTENT_AUTHORITY, "/example", 1);
uriMatcher.addURI(CONTENT_AUTHORITY, "/*", 2);
is the proper ordering, since the matcher will look for /example first before resorting to the /* match. If these
method calls were reversed and you called uriMatcher.match("/example"), then the UriMatcher will stop looking
for matches once it encounters the /* path and return the wrong result!
You will then need to override these functions:
onCreate(): Initialize your provider. The Android system calls this method immediately after it creates your
provider. Notice that your provider is not created until a ContentResolver object tries to access it.
@Override
public boolean onCreate() {
dbhelper = new DatabaseHelper(getContext());
return true;
}
getType(): Return the MIME type corresponding to a content URI
@Override
public String getType(Uri uri) {
final int match = matcher.match(uri);
GoalKicker.com – Android™ Notes for Professionals 670
switch (match) {
case DATA_TABLE:
return ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + MyContract.CONTENT_AUTHORITY + "/"
+ MyContract.PATH_DATATABLE;
case DATA_TABLE_DATE:
return ContentResolver.ANY_CURSOR_ITEM_TYPE + "/" + MyContract.CONTENT_AUTHORITY + "/"
+ MyContract.PATH_DATATABLE;
default:
throw new UnsupportedOperationException("Unknown Uri: " + uri);
}
}
query(): Retrieve data from your provider. Use the arguments to select the table to query, the rows and columns to
return, and the sort order of the result. Return the data as a Cursor object.
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String
sortOrder) {
Cursor retCursor = dbHelper.getReadableDatabase().query(
MyContract.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor;
}
Insert a new row into your provider. Use the arguments to select the destination table and to get the column values
to use. Return a content URI for the newly-inserted row.
@Override
public Uri insert(Uri uri, ContentValues values)
{
final SQLiteDatabase db = dbHelper.getWritableDatabase();
long id = db.insert(MyContract.TABLE_NAME, null, values);
return ContentUris.withAppendedId(MyContract.CONTENT_URI, ID);
}
delete(): Delete rows from your provider. Use the arguments to select the table and the rows to delete. Return the
number of rows deleted.
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int rowsDeleted = db.delete(MyContract.TABLE_NAME, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
update(): Update existing rows in your provider. Use the arguments to select the table and rows to update and to
get the new column values. Return the number of rows updated.
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int rowsUpdated = db.update(MyContract.TABLE_NAME, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
GoalKicker.com – Android™ Notes for Professionals 671
4) Update manifest file
<provider
android:authorities="com.example.myApp"
android:name=".DatabaseProvider"/>
GoalKicker.com – Android™ Notes for Professionals 672
Chapter 112: Dagger 2
Section 112.1: Component setup for Application and Activity
injection
A basic AppComponent that depends on a single AppModule to provide application-wide singleton objects.
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
void inject(App app);
Context provideContext();
Gson provideGson();
}
A module to use together with the AppComponent which will provide its singleton objects, e.g. an instance of Gson to
reuse throughout the whole application.
@Module
public class AppModule {
private final Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
@Singleton
@Provides
Gson provideGson() {
return new Gson();
}
@Singleton
@Provides
Context provideContext() {
return mApplication;
}
}
A subclassed application to setup dagger and the singleton component.
public class App extends Application {
@Inject
AppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
}
public AppComponent getAppComponent() {
GoalKicker.com – Android™ Notes for Professionals 673
return mAppComponent;
}
}
Now an activity scoped component that depends on the AppComponent to gain access to the singleton objects.
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}
And a reusable ActivityModule that will provide basic dependencies, like a FragmentManager
@Module
public class ActivityModule {
private final AppCompatActivity mActivity;
public ActivityModule(AppCompatActivity activity) {
mActivity = activity;
}
@ActivityScope
public AppCompatActivity provideActivity() {
return mActivity;
}
@ActivityScope
public FragmentManager provideFragmentManager(AppCompatActivity activity) {
return activity.getSupportFragmentManager();
}
}
Putting everything together we're set up and can inject our activity and be sure to use the same Gson throughout
out app!
public class MainActivity extends AppCompatActivity {
@Inject
Gson mGson;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainActivityComponent.builder()
.appComponent(((App)getApplication()).getAppComponent())
.activityModule(new ActivityModule(this))
.build().inject(this);
}
}
Section 112.2: Custom Scopes
@Scope
GoalKicker.com – Android™ Notes for Professionals 674
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {
}
Scopes are just annotations and you can create your own ones where needed.
Section 112.3: Using @Subcomponent instead of
@Component(dependencies={...})
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
void inject(App app);
Context provideContext();
Gson provideGson();
MainActivityComponent mainActivityComponent(ActivityModule activityModule);
}
@ActivityScope
@Subcomponent(modules = ActivityModule.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}
public class MainActivity extends AppCompatActivity {
@Inject
Gson mGson;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App)getApplication()).getAppComponent()
.mainActivityComponent(new ActivityModule(this)).inject(this);
}
}
Section 112.4: Creating a component from multiple modules
Dagger 2 supports creating a component from multiple modules. You can create your component this way:
@Singleton
@Component(modules = {GeneralPurposeModule.class, SpecificModule.class})
public interface MyMultipleModuleComponent {
void inject(MyFragment myFragment);
void inject(MyService myService);
void inject(MyController myController);
void inject(MyActivity myActivity);
}
The two references modules GeneralPurposeModule and SpecificModule can then be implemented as follows:
GeneralPurposeModule.java
GoalKicker.com – Android™ Notes for Professionals 675
@Module
public class GeneralPurposeModule {
@Provides
@Singleton
public Retrofit getRetrofit(PropertiesReader propertiesReader, RetrofitHeaderInterceptor
headerInterceptor){
// Logic here...
return retrofit;
}
@Provides
@Singleton
public PropertiesReader getPropertiesReader(){
return new PropertiesReader();
}
@Provides
@Singleton
public RetrofitHeaderInterceptor getRetrofitHeaderInterceptor(){
return new RetrofitHeaderInterceptor();
}
}
SpecificModule.java
@Singleton
@Module
public class SpecificModule {
@Provides @Singleton
public RetrofitController getRetrofitController(Retrofit retrofit){
RetrofitController retrofitController = new RetrofitController();
retrofitController.setRetrofit(retrofit);
return retrofitController;
}
@Provides @Singleton
public MyService getMyService(RetrofitController retrofitController){
MyService myService = new MyService();
myService.setRetrofitController(retrofitController);
return myService;
}
}
During the dependency injection phase, the component will take objects from both modules according to the
needs.
This approach is very useful in terms of modularity. In the example, there is a general purpose module used to
instantiate components such as the Retrofit object (used to handle the network communication) and a
PropertiesReader (in charge of handling configuration files). There is also a specific module that handles the
instantiation of specific controllers and service classes in relation to that specific application component.
Section 112.5: How to add Dagger 2 in build.gradle
Since the release of Gradle 2.2, the use of the android-apt plugin is no longer used. The following method of setting
up Dagger 2 should be used. For older version of Gradle, use the previous method shown below.
For Gradle >= 2.2
GoalKicker.com – Android™ Notes for Professionals 676
dependencies {
// apt command comes from the android-apt plugin
annotationProcessor 'com.google.dagger:dagger-compiler:2.8'
compile 'com.google.dagger:dagger:2.8'
provided 'javax.annotation:jsr250-api:1.0'
}
For Gradle < 2.2
To use Dagger 2 it's necessary to add android-apt plugin, add this to the root build.gradle:
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
Then the application module's build.gradle should contain:
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {

}
final DAGGER_VERSION = '2.0.2'
dependencies {

compile "com.google.dagger:dagger:${DAGGER_VERSION}"
apt "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
}
Reference: https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2
Section 112.6: Constructor Injection
Classes without dependencies can easily be created by dagger.
public class Engine {
@Inject // <-- Annotate your constructor.
public Engine() {
}
}
This class can be provided by any component. It has no dependencies itself and is not scoped. There is no further
code necessary.
Dependencies are declared as parameters in the constructor. Dagger will call the constructor and supply the
dependencies, as long as those dependencies can be provided.
public class Car {
GoalKicker.com – Android™ Notes for Professionals 677
private Engine engine;
@Inject
public Car(Engine engine) {
this.engine = engine;
}
}
This class can be provided by every component iff this component can also provide all of its dependencies—Engine
in this case. Since Engine can also be constructor injected, any component can provide a Car.
You can use constructor injection whenever all of the dependencies can be provided by the component. A
component can provide a dependency, if
it can create it by using constructor injection
a module of the component can provide it
it can be provided by the parent component (if it is a @Subcomponent)
it can use an object exposed by a component it depends on (component dependencies)
GoalKicker.com – Android™ Notes for Professionals 678
Chapter 113: Realm
Realm Mobile Database is an alternative to SQLite. Realm Mobile Database is much faster than an ORM, and often
faster than raw SQLite.
Benefits
Offline functionality, Fast queries, Safe threading, Cross-platform apps, Encryption, Reactive architecture.
Section 113.1: Sorted queries
In order to sort a query, instead of using findAll(), you should use findAllSorted().
RealmResults<SomeObject> results = realm.where(SomeObject.class)
.findAllSorted("sortField", Sort.ASCENDING);
Note:
sort() returns a completely new RealmResults that is sorted, but an update to this RealmResults will reset it. If you
use sort(), you should always re-sort it in your RealmChangeListener, remove the RealmChangeListener from the
previous RealmResults and add it to the returned new RealmResults. Using sort() on a RealmResults returned by
an async query that is not yet loaded will fail.
findAllSorted() will always return the results sorted by the field, even if it gets updated. It is recommended to use
findAllSorted().
Section 113.2: Using Realm with RxJava
For queries, Realm provides the realmResults.asObservable() method. Observing results is only possible on
looper threads (typically the UI thread).
For this to work, your configuration must contain the following
realmConfiguration = new RealmConfiguration.Builder(context) //
.rxFactory(new RealmObservableFactory()) //
//...
.build();
Afterwards, you can use your results as an observable.
Observable<RealmResults<SomeObject>> observable = results.asObservable();
For asynchronous queries, you should filter the results by isLoaded(), so that you receive an event only when the
query has been executed. This filter() is not needed for synchronous queries (isLoaded() always returns true
on sync queries).
Subscription subscription = RxTextView.textChanges(editText).switchMap(charSequence ->
realm.where(SomeObject.class)
.contains("searchField", charSequence.toString(), Case.INSENSITIVE)
.findAllAsync()
.asObservable())
.filter(RealmResults::isLoaded) //
.subscribe(objects -> adapter.updateData(objects));
GoalKicker.com – Android™ Notes for Professionals 679
For writes, you should either use the executeTransactionAsync() method, or open a Realm instance on the
background thread, execute the transaction synchronously, then close the Realm instance.
public Subscription loadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}
Section 113.3: Basic Usage
Setting up an instance
To use Realm you first need to obtain an instance of it. Each Realm instance maps to a file on disk. The most basic
way to get an instance is as follows:
// Create configuration
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(context).build();
// Obtain realm instance
Realm realm = Realm.getInstance(realmConfiguration);
// or
Realm.setDefaultConfiguration(realmConfiguration);
Realm realm = Realm.getDefaultInstance();
The method Realm.getInstance() creates the database file if it has not been created, otherwise opens the file. The
RealmConfiguration object controls all aspects of how a Realm is created - whether it's an inMemory() database,
name of the Realm file, if the Realm should be cleared if a migration is needed, initial data, etc.
Please note that calls to Realm.getInstance() are reference counted (each call increments a counter), and the
counter is decremented when realm.close() is called.
Closing an instance
On background threads, it's very important to close the Realm instance(s) once it's no longer used (for example,
transaction is complete and the thread execution ends). Failure to close all Realm instances on background thread
results in version pinning, and can cause a large growth in file size.
Runnable runnable = new Runnable() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ...
} finally {
if(realm != null) {
realm.close();
}
}
};
new Thread(runnable).start(); // background thread, like `doInBackground()` of AsyncTask
It's worth noting that above API Level 19, you can replace this code with just this:
GoalKicker.com – Android™ Notes for Professionals 680
try(Realm realm = Realm.getDefaultInstance()) {
// ...
}
Models
Next step would be creating your models. Here a question might be asked, "what is a model?". A model is a
structure which defines properties of an object being stored in the database. For example, in the following we
model a book.
public class Book extends RealmObject {
// Primary key of this entity
@PrimaryKey
private long id;
private String title;
@Index // faster queries
private String author;
// Standard getters & setter
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Note that your models should extend RealmObject class. Primary key is also specified by @PrimaryKey annotation.
Primary keys can be null, but only one element can have null as a primary key. Also you can use the @Ignore
annotation for the fields that should not be persisted to the disk:
@Ignore
private String isbn;
Inserting or updating data
In order to store a book object to your Realm database instance, you can first create an instance of your model and
then store it to the database via copyToRealm method. For creating or updating you can use copyToRealmOrUpdate.
(A faster alternative is the newly added insertOrUpdate()).
GoalKicker.com – Android™ Notes for Professionals 681
// Creating an instance of the model
Book book = new Book();
book.setId(1);
book.setTitle("Walking on air");
book.setAuthor("Taylor Swift")
// Store to the database
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.insertOrUpdate(book);
}
});
Note that all changes to data must happen in a transaction. Another way to create an object is using the following
pattern:
Book book = realm.createObject(Book.class, primaryKey);
...
Querying the database
All books:
RealmResults<Book> results = realm.where(Book.class).findAll();
All books having id greater than 10:
RealmResults<Book> results = realm.where(Book.class)
.greaterThan("id", 10)
.findAll();
Books by 'Taylor Swift' or '%Peter%':
RealmResults<Book> results = realm.where(Book.class)
.beginGroup()
.equalTo("author", "Taylor Swift")
.or()
.contains("author", "Peter")
.endGroup().findAll();
Deleting an object
For example, we want to delete all books by Taylor Swift:
// Start of transaction
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// First Step: Query all Taylor Swift books
RealmResults<Book> results = ...
// Second Step: Delete elements in Realm
results.deleteAllFromRealm();
}
});
GoalKicker.com – Android™ Notes for Professionals 682
Section 113.4: List of primitives (RealmList<Integer/String/...>)
Realm currently does not support storing a list of primitives. It is on their todo list (GitHub issue #575), but for the
meantime, here is a workaround.
Create a new class for your primitive type, this uses Integer, but change it for whatever you want to store.
public class RealmInteger extends RealmObject {
private int val;
public RealmInteger() {
}
public RealmInteger(int val) {
this.val = val;
}
// Getters and setters
}
You can now use this in your RealmObject.
public class MainObject extends RealmObject {
private String name;
private RealmList<RealmInteger> ints;
// Getters and setters
}
If you are using GSON to populate your RealmObject, you will need to add a custom type adapter.
Type token = new TypeToken<RealmList<RealmInteger>>(){}.getType();
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.registerTypeAdapter(token, new TypeAdapter<RealmList<RealmInteger>>() {
@Override
public void write(JsonWriter out, RealmList<RealmInteger> value) throws IOException {
// Empty
}
@Override
public RealmList<RealmInteger> read(JsonReader in) throws IOException {
RealmList<RealmInteger> list = new RealmList<RealmInteger>();
in.beginArray();
while (in.hasNext()) {
list.add(new RealmInteger(in.nextInt()));
}
in.endArray();
GoalKicker.com – Android™ Notes for Professionals 683
return list;
}
})
.create();
Section 113.5: Async queries
Every synchronous query method (such as findAll() or findAllSorted()) has an asynchronous counterpart
(findAllAsync() / findAllSortedAsync()).
Asynchronous queries offload the evaluation of the RealmResults to another thread. In order to receive these
results on the current thread, the current thread must be a looper thread (read: async queries typically only work
on the UI thread).
RealmChangeListener<RealmResults<SomeObject>> realmChangeListener; // field variable
realmChangeListener = new RealmChangeListener<RealmResults<SomeObject>>() {
@Override
public void onChange(RealmResults<SomeObject> element) {
// asyncResults are now loaded
adapter.updateData(element);
}
};
RealmResults<SomeObject> asyncResults = realm.where(SomeObject.class).findAllAsync();
asyncResults.addChangeListener(realmChangeListener);
Section 113.6: Adding Realm to your project
Add the following dependency to your project level build.gradle file.
dependencies {
classpath "io.realm:realm-gradle-plugin:3.1.2"
}
Add the following right at the top of your app level build.gradle file.
apply plugin: 'realm-android'
Complete a gradle sync and you now have Realm added as a dependency to your project!
Realm requires an initial call since 2.0.0 before using it. You can do this in your Application class or in your first
Activity's onCreate method.
Realm.init(this); // added in Realm 2.0.0
Realm.setDefaultConfiguration(new RealmConfiguration.Builder().build());
Section 113.7: Realm Models
Realm models must extend the RealmObject base class, they define the schema of the underlying database.
Supported field types are boolean, byte, short, int, long, float, double, String, Date, byte[], links to other
RealmObjects, and RealmList<T extends RealmModel>.
public class Person extends RealmObject {
GoalKicker.com – Android™ Notes for Professionals 684
@PrimaryKey //primary key is also implicitly an @Index
//it is required for `copyToRealmOrUpdate()` to update the object.
private long id;
@Index //index makes queries faster on this field
@Required //prevents `null` value from being inserted
private String name;
private RealmList<Dog> dogs; //->many relationship to Dog
private Person spouse; //->one relationship to Person
@Ignore
private Calendar birthday; //calendars are not supported but can be ignored
// getters, setters
}
If you add (or remove) a new field to your RealmObject (or you add a new RealmObject class or delete an existing
one), a migration will be needed. You can either set deleteIfMigrationNeeded() in your
RealmConfiguration.Builder, or define the necessary migration. Migration is also required when adding (or
removing) @Required, or @Index, or @PrimaryKey annotation.
Relationships must be set manually, they are NOT automatic based on primary keys.
Since 0.88.0, it is also possible to use public fields instead of private fields/getters/setters in RealmObject classes.
It is also possible to implement RealmModel instead of extending RealmObject, if the class is also annotated with
@RealmClass.
@RealmClass
public class Person implements RealmModel {
// ...
}
In that case, methods like person.deleteFromRealm() or person.addChangeListener() are replaced with
RealmObject.deleteFromRealm(person) and RealmObject.addChangeListener(person).
Limitations are that by a RealmObject, only RealmObject can be extended, and there is no support for final,
volatile and transient fields.
It is important that a managed RealmObject class can only be modified in a transaction. A managed RealmObject
cannot be passed between threads.
Section 113.8: try-with-resources
try (Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
//whatever Transaction that has to be done
}
});
//No need to close realm in try-with-resources
}
The Try with resources can be used only from KITKAT (minSDK 19)
GoalKicker.com – Android™ Notes for Professionals 685
Chapter 114: Android Versions
Section 114.1: Checking the Android Version on device at
runtime
Build.VERSION_CODES is an enumeration of the currently known SDK version codes.
In order to conditionally run code based on the device's Android version, use the TargetApi annotation to avoid
Lint errors, and check the build version before running the code specific to the API level.
Here is an example of how to use a class that was introduced in API-23, in a project that supports API levels lower
than 23:
@Override
@TargetApi(23)
public void onResume() {
super.onResume();
if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
//run Marshmallow code
FingerprintManager fingerprintManager = this.getSystemService(FingerprintManager.class);
//......................
}
}
GoalKicker.com – Android™ Notes for Professionals 686
Chapter 115: Wi-Fi Connections
Section 115.1: Connect with WEP encryption
This example connects to a Wi-Fi access point with WEP encryption, given an SSID and the password.
public boolean ConnectToNetworkWEP(String networkSSID, String password)
{
try {
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"" + networkSSID + "\""; // Please note the quotes. String should contain
SSID in quotes
conf.wepKeys[0] = "\"" + password + "\""; //Try it with quotes first
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
conf.allowedGroupCiphers.set(WifiConfiguration.AuthAlgorithm.OPEN);
conf.allowedGroupCiphers.set(WifiConfiguration.AuthAlgorithm.SHARED);
WifiManager wifiManager = (WifiManager)
this.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
int networkId = wifiManager.addNetwork(conf);
if (networkId == -1){
//Try it again with no quotes in case of hex password
conf.wepKeys[0] = password;
networkId = wifiManager.addNetwork(conf);
}
List<WifiConfiguration> list = wifiManager.getConfiguredNetworks();
for( WifiConfiguration i : list ) {
if(i.SSID != null && i.SSID.equals("\"" + networkSSID + "\"")) {
wifiManager.disconnect();
wifiManager.enableNetwork(i.networkId, true);
wifiManager.reconnect();
break;
}
}
//WiFi Connection success, return true
return true;
} catch (Exception ex) {
System.out.println(Arrays.toString(ex.getStackTrace()));
return false;
}
}
Section 115.2: Connect with WPA2 encryption
This example connects to a Wi-Fi access point with WPA2 encryption.
public boolean ConnectToNetworkWPA(String networkSSID, String password) {
try {
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"" + networkSSID + "\""; // Please note the quotes. String should contain SSID
in quotes
conf.preSharedKey = "\"" + password + "\"";
conf.status = WifiConfiguration.Status.ENABLED;
GoalKicker.com – Android™ Notes for Professionals 687
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
Log.d("connecting", conf.SSID + " " + conf.preSharedKey);
WifiManager wifiManager = (WifiManager)
this.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiManager.addNetwork(conf);
Log.d("after connecting", conf.SSID + " " + conf.preSharedKey);
List<WifiConfiguration> list = wifiManager.getConfiguredNetworks();
for( WifiConfiguration i : list ) {
if(i.SSID != null && i.SSID.equals("\"" + networkSSID + "\"")) {
wifiManager.disconnect();
wifiManager.enableNetwork(i.networkId, true);
wifiManager.reconnect();
Log.d("re connecting", i.SSID + " " + conf.preSharedKey);
break;
}
}
//WiFi Connection success, return true
return true;
} catch (Exception ex) {
System.out.println(Arrays.toString(ex.getStackTrace()));
return false;
}
}
Section 115.3: Scan for access points
This example scans for available access points and ad hoc networks. btnScan activates a scan initiated by the
WifiManager.startScan() method. After the scan, WifiManager calls the SCAN_RESULTS_AVAILABLE_ACTION intent
and the WifiScanReceiver class processes the scan result. The results are displayed in a TextView.
public class MainActivity extends AppCompatActivity {
private final static String TAG = "MainActivity";
TextView txtWifiInfo;
WifiManager wifi;
WifiScanReceiver wifiReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wifi=(WifiManager)getSystemService(Context.WIFI_SERVICE);
wifiReceiver = new WifiScanReceiver();
txtWifiInfo = (TextView)findViewById(R.id.txtWifiInfo);
Button btnScan = (Button)findViewById(R.id.btnScan);
btnScan.setOnClickListener(new View.OnClickListener() {
@Override
GoalKicker.com – Android™ Notes for Professionals 688
public void onClick(View v) {
Log.i(TAG, "Start scan...");
wifi.startScan();
}
});
}
protected void onPause() {
unregisterReceiver(wifiReceiver);
super.onPause();
}
protected void onResume() {
registerReceiver(
wifiReceiver,
new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
);
super.onResume();
}
private class WifiScanReceiver extends BroadcastReceiver {
public void onReceive(Context c, Intent intent) {
List<ScanResult> wifiScanList = wifi.getScanResults();
txtWifiInfo.setText("");
for(int i = 0; i < wifiScanList.size(); i++){
String info = ((wifiScanList.get(i)).toString());
txtWifiInfo.append(info+"\n\n");
}
}
}
}
Permissions
The following permissions need to be defined in AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
android.permission.ACCESS_WIFI_STATE is necessary for calling WifiManager.getScanResults(). Without
android.permission.CHANGE_WIFI_STATE you cannot initiate a scan with WifiManager.startScan().
When compiling the project for api level 23 or greater (Android 6.0 and up), either
android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION must be inserted.
Furthermore that permission needs to be requested, e.g. in the onCreate method of your main activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
String[] PERMS_INITIAL={
Manifest.permission.ACCESS_FINE_LOCATION,
};
ActivityCompat.requestPermissions(this, PERMS_INITIAL, 127);
}
GoalKicker.com – Android™ Notes for Professionals 689
Chapter 116: SensorManager
Section 116.1: Decide if your device is static or not, using the
accelerometer
Add the following code to the onCreate()/onResume() method:
SensorManager sensorManager;
Sensor mAccelerometer;
final float movementThreshold = 0.5f; // You may have to change this value.
boolean isMoving = false;
float[] prevValues = {1.0f, 1.0f, 1.0f};
float[] currValues = new float[3];
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
You may have to adjust the sensitivity by adapting the movementThreshold by trial and error. Then, override the
onSensorChanged() method as follows:
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor == mAccelerometer) {
System.arraycopy(event.values, 0, currValues, 0, event.values.length);
if ((Math.abs(currValues[0] - prevValues[0]) > movementThreshold) ||
(Math.abs(currValues[1] - prevValues[1]) > movementThreshold) ||
(Math.abs(currValues[2] - prevValues[2]) > movementThreshold)) {
isMoving = true;
} else {
isMoving = false;
}
System.arraycopy(currValues, 0, prevValues, 0, currValues.length);
}
}
If you want to prevent your app from being installed on devices that do not have an accelerometer, you have to add
the following line to your manifest:
<uses-feature android:name="android.hardware.sensor.accelerometer" />
Section 116.2: Retrieving sensor events
Retrieving sensor information from the onboard sensors:
public class MainActivity extends Activity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor accelerometer;
private Sensor gyroscope;
float[] accelerometerData = new float[3];
float[] gyroscopeData = new float[3];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GoalKicker.com – Android™ Notes for Professionals 690
setContentView(R.layout.activity_main);
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
gyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
}
@Override
public void onResume() {
//Register listeners for your sensors of interest
mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST);
mSensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_FASTEST);
super.onResume();
}
@Override
protected void onPause() {
//Unregister any previously registered listeners
mSensorManager.unregisterListener(this);
super.onPause();
}
@Override
public void onSensorChanged(SensorEvent event) {
//Check the type of sensor data being polled and store into corresponding float array
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
accelerometerData = event.values;
} else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
gyroscopeData = event.values;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
}
Section 116.3: Sensor transformation to world coordinate
system
The sensor values returned by Android are with respective to the phone's coordinate system (e.g. +Y points towards
the top of the phone). We can transform these sensor values into a world coordinate system (e.g. +Y points towards
magnetic North, tangential to the ground) using the sensor managers rotation matrix
First, you would need to declare and initialize the matrices/arrays where data will be stored (you can do this in the
onCreate method, for example):
float[] accelerometerData = new float[3];
float[] accelerometerWorldData = new float[3];
float[] gravityData = new float[3];
float[] magneticData = new float[3];
float[] rotationMatrix = new float[9];
Next, we need to detect changes in sensor values, store them into the corresponding arrays (if we want to use them
later/elsewhere), then calculate the rotation matrix and resulting transformation into world coordinates:
GoalKicker.com – Android™ Notes for Professionals 691
public void onSensorChanged(SensorEvent event) {
sensor = event.sensor;
int i = sensor.getType();
if (i == Sensor.TYPE_ACCELEROMETER) {
accelerometerData = event.values;
} else if (i == Sensor.TYPE_GRAVITY) {
gravityData = event.values;
} else if (i == Sensor.TYPE_MAGNETIC) {
magneticData = event.values;
}
//Calculate rotation matrix from gravity and magnetic sensor data
SensorManager.getRotationMatrix(rotationMatrix, null, gravityData, magneticData);
//World coordinate system transformation for acceleration
accelerometerWorldData[0] = rotationMatrix[0] * accelerometerData[0] + rotationMatrix[1] *
accelerometerData[1] + rotationMatrix[2] * accelerometerData[2];
accelerometerWorldData[1] = rotationMatrix[3] * accelerometerData[0] + rotationMatrix[4] *
accelerometerData[1] + rotationMatrix[5] * accelerometerData[2];
accelerometerWorldData[2] = rotationMatrix[6] * accelerometerData[0] + rotationMatrix[7] *
accelerometerData[1] + rotationMatrix[8] * accelerometerData[2];
}
GoalKicker.com – Android™ Notes for Professionals 692
Chapter 117: ProgressBar
Section 117.1: Material Linear ProgressBar
According to Material Documentation:
A linear progress indicator should always fill from 0% to 100% and never decrease in value.
It should be represented by bars on the edge of a header or sheet that appear and disappear.
To use a material Linear ProgressBar just use in your xml:
<ProgressBar
android:id="@+id/my_progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Indeterminate
To create indeterminate ProgressBar set the android:indeterminate attribute to true.
<ProgressBar
GoalKicker.com – Android™ Notes for Professionals 693
android:id="@+id/my_progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
Determinate
To create determinate ProgressBar set the android:indeterminate attribute to false and use the android:max
and the android:progress attributes:
<ProgressBar
android:id="@+id/my_progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:indeterminate="false"
android:max="100"
android:progress="10"/>
Just use this code to update the value:
ProgressBar progressBar = (ProgressBar) findViewById(R.id.my_progressBar);
progressBar.setProgress(20);
Buffer
To create a buffer effect with the ProgressBar set the android:indeterminate attribute to false and use the
android:max, the android:progress and the android:secondaryProgress attributes:
<ProgressBar
android:id="@+id/my_progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="false"
android:max="100"
android:progress="10"
android:secondaryProgress="25"/>
The buffer value is defined by android:secondaryProgress attribute.
Just use this code to update the values:
ProgressBar progressBar = (ProgressBar) findViewById(R.id.my_progressBar);
progressBar.setProgress(20);
progressBar.setSecondaryProgress(50);
Indeterminate and Determinate
To obtain this kind of ProgressBar just use an indeterminate ProgressBar using the android:indeterminate
attribute to true.
<ProgressBar
android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:indeterminate="true"/>
Then when you need to switch from indeterminate to determinate progress use setIndeterminate() method .
ProgressBar progressBar = (ProgressBar) findViewById(R.id.my_progressBar);
progressBar.setIndeterminate(false);
GoalKicker.com – Android™ Notes for Professionals 694
Section 117.2: Tinting ProgressBar
Using an AppCompat theme, the ProgressBar's color will be the colorAccent you have defined.
Version ≥ 5.0
To change the ProgressBar color without changing the accent color you can use theandroid:theme attribute
overriding the accent color:
<ProgressBar
android:theme="@style/MyProgress"
style="@style/Widget.AppCompat.ProgressBar" />
<!-- res/values/styles.xml -->
<style name="MyProgress" parent="Theme.AppCompat.Light">
<item name="colorAccent">@color/myColor</item>
</style>
To tint the ProgressBar you can use in the xml file the attributes android:indeterminateTintMode and
android:indeterminateTint
<ProgressBar
android:indeterminateTintMode="src_in"
android:indeterminateTint="@color/my_color"
/>
Section 117.3: Customized progressbar
CustomProgressBarActivity.java:
public class CustomProgressBarActivity extends AppCompatActivity {
private TextView txtProgress;
private ProgressBar progressBar;
private int pStatus = 0;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_progressbar);
txtProgress = (TextView) findViewById(R.id.txtProgress);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
new Thread(new Runnable() {
@Override
public void run() {
while (pStatus <= 100) {
handler.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(pStatus);
txtProgress.setText(pStatus + " %");
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
GoalKicker.com – Android™ Notes for Professionals 695
e.printStackTrace();
}
pStatus++;
}
}
}).start();
}
}
activity_custom_progressbar.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.skholingua.android.custom_progressbar_circular.MainActivity" >
<RelativeLayout
android:layout_width="wrap_content"
android:layout_centerInParent="true"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_centerInParent="true"
android:indeterminate="false"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/custom_progressbar_drawable"
android:secondaryProgress="0" />
<TextView
android:id="@+id/txtProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/progressBar"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
</RelativeLayout>
custom_progressbar_drawable.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="-90"
android:pivotX="50%"
android:pivotY="50%"
GoalKicker.com – Android™ Notes for Professionals 696
android:toDegrees="270" >
<shape
android:shape="ring"
android:useLevel="false" >
<gradient
android:centerY="0.5"
android:endColor="#FA5858"
android:startColor="#0099CC"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
Reference screenshot:
Section 117.4: Creating Custom Progress Dialog
By Creating Custom Progress Dialog class, the dialog can be used to show in UI instance, without recreating the
dialog.
First Create a Custom Progress Dialog Class.
GoalKicker.com – Android™ Notes for Professionals 697
CustomProgress.java
public class CustomProgress {
public static CustomProgress customProgress = null;
private Dialog mDialog;
public static CustomProgress getInstance() {
if (customProgress == null) {
customProgress = new CustomProgress();
}
return customProgress;
}
public void showProgress(Context context, String message, boolean cancelable) {
mDialog = new Dialog(context);
// no tile for the dialog
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
mDialog.setContentView(R.layout.prograss_bar_dialog);
mProgressBar = (ProgressBar) mDialog.findViewById(R.id.progress_bar);
// mProgressBar.getIndeterminateDrawable().setColorFilter(context.getResources()
// .getColor(R.color.material_blue_gray_500), PorterDuff.Mode.SRC_IN);
TextView progressText = (TextView) mDialog.findViewById(R.id.progress_text);
progressText.setText("" + message);
progressText.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.VISIBLE);
// you can change or add this line according to your need
mProgressBar.setIndeterminate(true);
mDialog.setCancelable(cancelable);
mDialog.setCanceledOnTouchOutside(cancelable);
mDialog.show();
}
public void hideProgress() {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
}
}
Now creating the custom progress layout
prograss_bar_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="65dp"
android:background="@android:color/background_dark"
android:orientation="vertical">
<TextView
android:id="@+id/progress_text"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_above="@+id/progress_bar"
android:layout_marginLeft="10dp"
GoalKicker.com – Android™ Notes for Professionals 698
android:layout_marginStart="10dp"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:text=""
android:textColor="@android:color/white"
android:textSize="16sp"
android:visibility="gone" />
<-- Where the style can be changed to any kind of ProgressBar -->
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_gravity="center"
android:background="@color/cardview_dark_background"
android:maxHeight="20dp"
android:minHeight="20dp" />
</RelativeLayout>
This is it. Now for calling the Dialog in Code
CustomProgress customProgress = CustomProgress.getInstance();
// now you have the instance of CustomProgres
// for showing the ProgressBar
customProgress.showProgress(#Context, getString(#StringId), #boolean);
// for hiding the ProgressBar
customProgress.hideProgress();
Section 117.5: Indeterminate ProgressBar
An indeterminate ProgressBar shows a cyclic animation without an indication of progress.
Basic indeterminate ProgressBar (spinning wheel)
<ProgressBar
android:id="@+id/progressBar"
android:indeterminate="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Horizontal indeterminate ProgressBar (flat bar)
<ProgressBar
android:id="@+id/progressBar"
android:indeterminate="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"/>
GoalKicker.com – Android™ Notes for Professionals 699
Other built-in ProgressBar styles
style="@android:style/Widget.ProgressBar.Small"
style="@android:style/Widget.ProgressBar.Large"
style="@android:style/Widget.ProgressBar.Inverse"
style="@android:style/Widget.ProgressBar.Small.Inverse"
style="@android:style/Widget.ProgressBar.Large.Inverse"
To use the indeterminate ProgressBar in an Activity
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
Section 117.6: Determinate ProgressBar
A determinate ProgressBar shows the current progress towards a specific maximum value.
Horizontal determinate ProgressBar
<ProgressBar
android:id="@+id/progressBar"
android:indeterminate="false"
android:layout_width="match_parent"
android:layout_height="10dp"
style="@android:style/Widget.ProgressBar.Horizontal"/>
Vertical determinate ProgressBar
<ProgressBar
android:id="@+id/progressBar"
android:indeterminate="false"
android:layout_width="10dp"
android:layout_height="match_parent"
android:progressDrawable="@drawable/progress_vertical"
style="@android:style/Widget.ProgressBar.Horizontal"/>
res/drawable/progress_vertical.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="3dp"/>
<solid android:color="@android:color/darker_gray"/>
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip android:clipOrientation="vertical" android:gravity="bottom">
<shape>
<corners android:radius="3dp"/>
<solid android:color="@android:color/holo_blue_light"/>
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip android:clipOrientation="vertical" android:gravity="bottom">
<shape>
GoalKicker.com – Android™ Notes for Professionals 700
<corners android:radius="3dp"/>
<solid android:color="@android:color/holo_blue_dark"/>
</shape>
</clip>
</item>
</layer-list>
Ring determinate ProgressBar
<ProgressBar
android:id="@+id/progressBar"
android:indeterminate="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progressDrawable="@drawable/progress_ring"
style="@android:style/Widget.ProgressBar.Horizontal"/>
res/drawable/progress_ring.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/secondaryProgress">
<shape
android:shape="ring"
android:useLevel="true"
android:thicknessRatio="24"
android:innerRadiusRatio="2.2">
<corners android:radius="3dp"/>
<solid android:color="#0000FF"/>
</shape>
</item>
<item android:id="@android:id/progress">
<shape
android:shape="ring"
android:useLevel="true"
android:thicknessRatio="24"
android:innerRadiusRatio="2.2">
<corners android:radius="3dp"/>
<solid android:color="#FFFFFF"/>
</shape>
</item>
</layer-list>
To use the determinate ProgressBar in an Activity.
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setSecondaryProgress(100);
progressBar.setProgress(10);
progressBar.setMax(100);
GoalKicker.com – Android™ Notes for Professionals 701
Chapter 118: Custom Fonts
Section 118.1: Custom font in canvas text
Drawing text in canvas with your font from assets.
Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/SomeFont.ttf");
Paint textPaint = new Paint();
textPaint.setTypeface(typeface);
canvas.drawText("Your text here", x, y, textPaint);
Section 118.2: Working with fonts in Android O
Android O changes the way to work with fonts.
Android O introduces a new feature, called Fonts in XML, which allows you to use fonts as resources. This means,
that there is no need to bundle fonts as assets. Fonts are now compiled in an R file and are automatically available
in the system as a resource.
In order to add a new font, you have to do the following:
Create a new resource directory: res/font.
Add your font files into this font folder. For example, by adding myfont.ttf, you will be able to use this font
via R.font.myfont.
You can also create your own font family by adding the following XML file into the res/font directory:
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font
android:fontStyle="normal"
android:fontWeight="400"
android:font="@font/lobster_regular" />
<font
android:fontStyle="italic"
android:fontWeight="400"
android:font="@font/lobster_italic" />
</font-family>
You can use both the font file and the font family file in the same way:
In an XML file, by using the android:fontFamily attribute, for example like this:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/myfont"/>
Or like this:
<style name="customfontstyle" parent="@android:style/TextAppearance.Small">
<item name="android:fontFamily">@font/myfont</item>
</style>
GoalKicker.com – Android™ Notes for Professionals 702
In your code, by using the following lines of code:
Typeface typeface = getResources().getFont(R.font.myfont);
textView.setTypeface(typeface);
Section 118.3: Custom font to whole activity
public class ReplaceFont {
public static void changeDefaultFont(Context context, String oldFont, String assetsFont) {
Typeface typeface = Typeface.createFromAsset(context.getAssets(), assetsFont);
replaceFont(oldFont, typeface);
}
private static void replaceFont(String oldFont, Typeface typeface) {
try {
Field myField = Typeface.class.getDeclaredField(oldFont);
myField.setAccessible(true);
myField.set(null, typeface);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Then in your activity, in onCreate() method:
// Put your font to assets folder...
ReplaceFont.changeDefaultFont(getApplication(), "DEFAULT", "LinLibertine.ttf");
Section 118.4: Putting a custom font in your app
1. Go to the (project folder)
2. Then app -> src -> main.
3. Create folder 'assets -> fonts' into the main folder.
4. Put your 'fontfile.ttf' into the fonts folder.
Section 118.5: Initializing a font
private Typeface myFont;
// A good practice might be to call this in onCreate() of a custom
// Application class and pass 'this' as Context. Your font will be ready to use
// as long as your app lives
public void initFont(Context context) {
myFont = Typeface.createFromAsset(context.getAssets(), "fonts/Roboto-Light.ttf");
}
Section 118.6: Using a custom font in a TextView
public void setFont(TextView textView) {
textView.setTypeface(myFont);
}
GoalKicker.com – Android™ Notes for Professionals 703
Section 118.7: Apply font on TextView by xml (Not required
Java code)
TextViewPlus.java:
public class TextViewPlus extends TextView {
private static final String TAG = "TextView";
public TextViewPlus(Context context) {
super(context);
}
public TextViewPlus(Context context, AttributeSet attrs) {
super(context, attrs);
setCustomFont(context, attrs);
}
public TextViewPlus(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setCustomFont(context, attrs);
}
private void setCustomFont(Context ctx, AttributeSet attrs) {
TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.TextViewPlus);
String customFont = a.getString(R.styleable.TextViewPlus_customFont);
setCustomFont(ctx, customFont);
a.recycle();
}
public boolean setCustomFont(Context ctx, String asset) {
Typeface typeface = null;
try {
typeface = Typeface.createFromAsset(ctx.getAssets(), asset);
} catch (Exception e) {
Log.e(TAG, "Unable to load typeface: "+e.getMessage());
return false;
}
setTypeface(typeface);
return true;
}
}
attrs.xml: (Where to place res/values)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextViewPlus">
<attr name="customFont" format="string"/>
</declare-styleable>
</resources>
How to use:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:foo="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
GoalKicker.com – Android™ Notes for Professionals 704
<com.mypackage.TextViewPlus
android:id="@+id/textViewPlus1"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:text="@string/showingOffTheNewTypeface"
foo:customFont="my_font_name_regular.otf">
</com.mypackage.TextViewPlus>
</LinearLayout>
Section 118.8: Ecient Typeface loading
Loading custom fonts can be lead to a bad performance. I highly recommend to use this little helper which
saves/loads your already used fonts into a Hashtable.
public class TypefaceUtils {
private static final Hashtable<String, Typeface> sTypeFaces = new Hashtable<>();
/**
* Get typeface by filename from assets main directory
*
* @param context
* @param fileName the name of the font file in the asset main directory
* @return
*/
public static Typeface getTypeFace(final Context context, final String fileName) {
Typeface tempTypeface = sTypeFaces.get(fileName);
if (tempTypeface == null) {
tempTypeface = Typeface.createFromAsset(context.getAssets(), fileName);
sTypeFaces.put(fileName, tempTypeface);
}
return tempTypeface;
}
}
Usage:
Typeface typeface = TypefaceUtils.getTypeface(context, "RobotoSlab-Bold.ttf");
setTypeface(typeface);
GoalKicker.com – Android™ Notes for Professionals 705
Chapter 119: Getting system font names
and using the fonts
The following examples show how to retrieve the default names of the system fonts that are store in the
/system/fonts/ directory and how to use a system font to set the typeface of a TextView element.
Section 119.1: Getting system font names
ArrayList<String> fontNames = new ArrayList<String>();
File temp = new File("/system/fonts/");
String fontSuffix = ".ttf";
for(File font : temp.listFiles()) {
String fontName = font.getName();
if(fontName.endsWith(fontSuffix)) {
fontNames.add(fontName.subSequence(0,fontName.lastIndexOf(fontSuffix)).toString());
}
}
Section 119.2: Applying a system font to a TextView
In the following code you need to replace fontsname by the name of the font you would like to use:
TextView lblexample = (TextView) findViewById(R.id.lblexample);
lblexample.setTypeface(Typeface.createFromFile("/system/fonts/" + "fontsname" + ".ttf"));
GoalKicker.com – Android™ Notes for Professionals 706
Chapter 120: Text to Speech(TTS)
Section 120.1: Text to Speech Base
layout_text_to_speech.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter text here!"
android:id="@+id/textToSpeak"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/textToSpeak"
android:id="@+id/btnSpeak"/>
</RelativeLayout>
AndroidTextToSpeechActivity.java
public class AndroidTextToSpeechActivity extends Activity implements
TextToSpeech.OnInitListener {
EditText textToSpeak = null;
Button btnSpeak = null;
TextToSpeech tts;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textToSpeak = findViewById(R.id.textToSpeak);
btnSpeak = findViewById(R.id.btnSpeak);
btnSpeak.setEnabled(false);
tts = new TextToSpeech(this, this);
btnSpeak.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
speakOut();
}
});
}
@Override
public void onDestroy() {
// Don't forget to shutdown tts!
if (tts != null) {
tts.stop();
tts.shutdown();
}
super.onDestroy();
}
GoalKicker.com – Android™ Notes for Professionals 707
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = tts.setLanguage(Locale.US);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("TTS", "This Language is not supported");
} else {
btnSpeak.setEnabled(true);
speakOut();
}
} else {
Log.e("TTS", "Initilization Failed!");
}
}
private void speakOut() {
String text = textToSpeak.getText().toString();
if(text == null || text.isEmpty())
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String utteranceId=this.hashCode() + "";
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId);
} else {
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
}
}
}
The language to be spoken can be set by providing a Locale to the setLanguage() method:
tts.setLanguage(Locale.CHINESE); // Chinese language
The number of supported languages varies between Android levels. The method isLanguageAvailable() can be
used to check if a certain language is supported:
tts.isLanguageAvailable(Locale.CHINESE);
The speech pitch level can be set by using the setPitch() method. By default, the pitch value is 1.0. Use values less
than 1.0 to decrease the pitch level or values greater than 1.0 to increase the pitch level:
tts.setPitch(0.6);
The speech rate can be set using setSpeechRate(). The default speech rate is 1.0. The speech rate can be doubled
by setting it to 2.0 or made half by setting it to 0.5:
tts.setSpeechRate(2.0);
Section 120.2: TextToSpeech implementation across the APIs
Cold observable implementation, emits true when TTS engine finishes speaking, starts speaking when subscribed.
Notice that API level 21 introduces different way to perform speaking:
public class RxTextToSpeech {
GoalKicker.com – Android™ Notes for Professionals 708
@Nullable RxTTSObservableOnSubscribe audio;
WeakReference<Context> contextRef;
public RxTextToSpeech(Context context) {
this.contextRef = new WeakReference<>(context);
}
public void requestTTS(FragmentActivity activity, int requestCode) {
Intent checkTTSIntent = new Intent();
checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
activity.startActivityForResult(checkTTSIntent, requestCode);
}
public void cancelCurrent() {
if (audio != null) {
audio.dispose();
audio = null;
}
}
public Observable<Boolean> speak(String textToRead) {
audio = new RxTTSObservableOnSubscribe(contextRef.get(), textToRead, Locale.GERMANY);
return Observable.create(audio);
}
public static class RxTTSObservableOnSubscribe extends UtteranceProgressListener
implements ObservableOnSubscribe<Boolean>,
Disposable, Cancellable, TextToSpeech.OnInitListener {
volatile boolean disposed;
ObservableEmitter<Boolean> emitter;
TextToSpeech textToSpeech;
String text = "";
Locale selectedLocale;
Context context;
public RxTTSObservableOnSubscribe(Context context, String text, Locale locale) {
this.selectedLocale = locale;
this.context = context;
this.text = text;
}
@Override public void subscribe(ObservableEmitter<Boolean> e) throws Exception {
this.emitter = e;
if (context == null) {
this.emitter.onError(new Throwable("nullable context, cannot execute " + text));
} else {
this.textToSpeech = new TextToSpeech(context, this);
}
}
@Override @DebugLog public void dispose() {
if (textToSpeech != null) {
textToSpeech.setOnUtteranceProgressListener(null);
textToSpeech.stop();
textToSpeech.shutdown();
textToSpeech = null;
}
disposed = true;
}
GoalKicker.com – Android™ Notes for Professionals 709
@Override public boolean isDisposed() {
return disposed;
}
@Override public void cancel() throws Exception {
dispose();
}
@Override public void onInit(int status) {
int languageCode = textToSpeech.setLanguage(selectedLocale);
if (languageCode == android.speech.tts.TextToSpeech.LANG_COUNTRY_AVAILABLE) {
textToSpeech.setPitch(1);
textToSpeech.setSpeechRate(1.0f);
textToSpeech.setOnUtteranceProgressListener(this);
performSpeak();
} else {
emitter.onError(new Throwable("language " + selectedLocale.getCountry() + " is not
supported"));
}
}
@Override public void onStart(String utteranceId) {
//no-op
}
@Override public void onDone(String utteranceId) {
this.emitter.onNext(true);
this.emitter.onComplete();
}
@Override public void onError(String utteranceId) {
this.emitter.onError(new Throwable("error TTS " + utteranceId));
}
void performSpeak() {
if (isAtLeastApiLevel(21)) {
speakWithNewApi();
} else {
speakWithOldApi();
}
}
@RequiresApi(api = 21) void speakWithNewApi() {
Bundle params = new Bundle();
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "");
textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, params, uniqueId());
}
void speakWithOldApi() {
HashMap<String, String> map = new HashMap<>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, uniqueId());
textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, map);
}
private String uniqueId() {
return UUID.randomUUID().toString();
}
}
GoalKicker.com – Android™ Notes for Professionals 710
public static boolean isAtLeastApiLevel(int apiLevel) {
return Build.VERSION.SDK_INT >= apiLevel;
}
}
GoalKicker.com – Android™ Notes for Professionals 711
Chapter 121: Spinner
Section 121.1: Basic Spinner Example
Spinner It is a type of dropdown input. Firstly in layout
<Spinner
android:id="@+id/spinner" <!-- id to refer this spinner from JAVA-->
android:layout_width="match_parent"
android:layout_height="wrap_content">
</Spinner>
Now Secondly populate values in spinner There are mainly two ways to populate values in spinner.
1. From XML itself create a array.xml in values directory under res. Create this array
<string-array name="defaultValue">
<item>--Select City Area--</item>
<item>--Select City Area--</item>
<item>--Select City Area--</item>
</string-array>
Now add this line in sppiner XML
android:entries="@array/defaultValue"
2. You can also add values via JAVA
if you are using in activity cityArea = (Spinner) findViewById(R.id.cityArea); else if you are using in fragment
cityArea = (Spinner) findViewById(R.id.cityArea);
Now create a arrayList of Strings
ArrayList<String> area = new ArrayList<>();
//add values in area arrayList
cityArea.setAdapter(new ArrayAdapter<String>(context
, android.R.layout.simple_list_item_1, area));
This will look like
According to the device Android version it will render style
GoalKicker.com – Android™ Notes for Professionals 712
Following are some of the default themes
If an app does not explicitly request a theme in its manifest, Android System will determine the default theme
based on the app’s targetSdkVersion to maintain the app’s original expectations:
Android SDK Version Default Theme
Version < 11 @android:style/Theme
Version between 11 and 13 @android:style/Theme.Holo
14 and higher @android:style/Theme.DeviceDefault
Spinner can be easily customized with the help of xml eg
android:background="@drawable/spinner_background"
android:layout_margin="16dp"
android:padding="16dp"
Create a custom background in XML and use it.
easily get the position and other details of the selected item in spinner
cityArea.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
areaNo = position;
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
Change the text color of the selected item in spinner
This can be done in two ways in XML
<item android:state_activated="true" android:color="@color/red"/>
This will change the selected item color in the popup.
and from JAVA do this (in the setOnItemSelectedListener(...))
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
((TextView) parent.getChildAt(0)).setTextColor(0x00000000);
// similarly change `background color` etc.
}
Section 121.2: Adding a spinner to your activity
In /res/values/strings.xml:
<string-array name="spinner_options">
<item>Option 1</item>
GoalKicker.com – Android™ Notes for Professionals 713
<item>Option 2</item>
<item>Option 3</item>
</string-array>
In layout XML:
<Spinner
android:id="@+id/spinnerName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/spinner_options" />
In Activity:
Spinner spinnerName = (Spinner) findViewById(R.id.spinnerName);
spinnerName.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String chosenOption = (String) parent.getItemAtPosition(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
GoalKicker.com – Android™ Notes for Professionals 714
Chapter 122: Data Encryption/Decryption
This topic discusses how encryption and decryption works in Android.
Section 122.1: AES encryption of data using password in a
secure way
The following example encrypts a given data block using AES. The encryption key is derived in a secure way
(random salt, 1000 rounds of SHA-256). The encryption uses AES in CBC mode with random IV.
Note that the data stored in the class EncryptedData (salt, iv, and encryptedData) can be concatenated to a single
byte array. You can then save the data or transmit it to the recipient.
private static final int SALT_BYTES = 8;
private static final int PBK_ITERATIONS = 1000;
private static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String PBE_ALGORITHM = "PBEwithSHA256and128BITAES-CBC-BC";
private EncryptedData encrypt(String password, byte[] data) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, InvalidAlgorithmParameterException {
EncryptedData encData = new EncryptedData();
SecureRandom rnd = new SecureRandom();
encData.salt = new byte[SALT_BYTES];
encData.iv = new byte[16]; // AES block size
rnd.nextBytes(encData.salt);
rnd.nextBytes(encData.iv);
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), encData.salt, PBK_ITERATIONS);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(PBE_ALGORITHM);
Key key = secretKeyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(encData.iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
encData.encryptedData = cipher.doFinal(data);
return encData;
}
private byte[] decrypt(String password, byte[] salt, byte[] iv, byte[] encryptedData) throws
NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, PBK_ITERATIONS);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(PBE_ALGORITHM);
Key key = secretKeyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
return cipher.doFinal(encryptedData);
}
private static class EncryptedData {
public byte[] salt;
public byte[] iv;
public byte[] encryptedData;
}
The following example code shows how to test encryption and decryption:
GoalKicker.com – Android™ Notes for Professionals 715
try {
String password = "test12345";
byte[] data = "plaintext11223344556677889900".getBytes("UTF-8");
EncryptedData encData = encrypt(password, data);
byte[] decryptedData = decrypt(password, encData.salt, encData.iv, encData.encryptedData);
String decDataAsString = new String(decryptedData, "UTF-8");
Toast.makeText(this, decDataAsString, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
GoalKicker.com – Android™ Notes for Professionals 716
Chapter 123: OkHttp
Section 123.1: Basic usage example
I like to wrap my OkHttp into a class called HttpClient for example, and in this class I have methods for each of the
major HTTP verbs, post, get, put and delete, most commonly. (I usually include an interface, in order to keep for it
to implement, in order to be able to easily change to a different implementation, if need be):
public class HttpClient implements HttpClientInterface{
private static final String TAG = OkHttpClient.class.getSimpleName();
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient httpClient = new OkHttpClient();
@Override
public String post(String url, String json) throws IOException {
Log.i(TAG, "Sending a post request with body:\n" + json + "\n to URL: " + url);
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = httpClient.newCall(request).execute();
return response.body().string();
}
The syntax is the same for put, get and delete except for 1 word (.put(body)) so it might be obnoxious to post
that code as well. Usage is pretty simple, just call the appropriate method on some url with some json payload and
the method will return a string as a result that you can later use and parse. Let's assume that the response will be a
json, we can create a JSONObject easily from it:
String response = httpClient.post(MY_URL, JSON_PAYLOAD);
JSONObject json = new JSONObject(response);
// continue to parse the response according to it's structure
Section 123.2: Setting up OkHttp
Grab via Maven:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.6.0</version>
</dependency>
or Gradle:
compile 'com.squareup.okhttp3:okhttp:3.6.0'
Section 123.3: Logging interceptor
Interceptors are used to intercept OkHttp calls. The reason to intercept could be to monitor, rewrite and retry
GoalKicker.com – Android™ Notes for Professionals 717
calls. It can be used for outgoing request or incoming response both.
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
Section 123.4: Synchronous Get Call
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(yourUrl)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
System.out.println(response.body().string());
}
Section 123.5: Asynchronous Get Call
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(yourUrl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
System.out.println(response.body().string());
}
GoalKicker.com – Android™ Notes for Professionals 718
});
}
Section 123.6: Posting form parameters
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
Section 123.7: Posting a multipart request
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
Section 123.8: Rewriting Responses
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=60")
.build();
}
GoalKicker.com – Android™ Notes for Professionals 719
};
GoalKicker.com – Android™ Notes for Professionals 720
Chapter 124: Handling Deep Links
<data> Attribute Details
scheme The scheme part of a URI (case-sensitive). Examples: http, https, ftp
host The host part of a URI (case-sensitive). Examples: google.com, example.org
port The port part of a URI. Examples: 80, 443
path The path part of a URI. Must begin with /. Examples: /, /about
pathPrefix A prefix for the path part of a URI. Examples: /item, /article
pathPattern A pattern to match for the path part of a URI. Examples: /item/.*, /article/[0-9]*
mimeType A mime type to match. Examples: image/jpeg, audio/*
Deep links are URLs that take users directly to specific content in your app. You can set up deep links by adding
intent filters and extracting data from incoming intents to drive users to the right screen in your app.
Section 124.1: Retrieving query parameters
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = getIntent();
Uri data = intent.getData();
if (data != null) {
String param1 = data.getQueryParameter("param1");
String param2 = data.getQueryParameter("param2");
}
}
}
If the user clicks on a linkto http://www.example.com/map?param1=FOO&param2=BAR, then param1 here will have a
value of "FOO" and param2 will have a value of "BAR".
Section 124.2: Simple deep link
AndroidManifest.xml:
<activity android:name="com.example.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"
android:host="www.example.com" />
</intent-filter>
</activity>
GoalKicker.com – Android™ Notes for Professionals 721
This will accept any link starting with http://www.example.com as a deep link to start your MainActivity.
Section 124.3: Multiple paths on a single domain
AndroidManifest.xml:
<activity android:name="com.example.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"
android:host="www.example.com" />
<data android:path="/" />
<data android:path="/about" />
<data android:path="/map" />
</intent-filter>
</activity>
This will launch your MainActivity when the user clicks any of these links:
http://www.example.com/
http://www.example.com/about
http://www.example.com/map
Section 124.4: Multiple domains and multiple paths
AndroidManifest.xml:
<activity android:name="com.example.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"
android:host="www.example.com" />
<data android:scheme="http"
android:host="www.example2.com" />
<data android:path="/" />
<data android:path="/map" />
</intent-filter>
</activity>
This will launch your MainActivity when the user clicks any of these links:
http://www.example.com/
http://www.example2.com/
GoalKicker.com – Android™ Notes for Professionals 722
http://www.example.com/map
http://www.example2.com/map
Section 124.5: Both http and https for the same domain
AndroidManifest.xml:
<activity android:name="com.example.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="www.example.com" />
<data android:path="/" />
<data android:path="/map" />
</intent-filter>
</activity>
This will launch your MainActivity when the user clicks any of these links:
http://www.example.com/
https://www.example.com/
http://www.example.com/map
https://www.example.com/map
Section 124.6: Using pathPrefix
AndroidManifest.xml:
<activity android:name="com.example.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"
android:host="www.example.com"
android:path="/item" />
</intent-filter>
</activity>
This will launch your MainActivity when the user clicks any link starting with http://www.example.com/item, such as:
https://www.example.com/item
http://www.example.com/item/1234
https://www.example.com/item/xyz/details
GoalKicker.com – Android™ Notes for Professionals 723
Chapter 125: Crash Reporting Tools
Section 125.1: Fabric - Crashlytics
Fabric is a modular mobile platform that provides useful kits you can mix to build your application. Crashlytics is a
crash and issue reporting tool provided by Fabric that allows you to track and monitor your applications in detail.
How to Configure Fabric-Crashlytics
Step 1: Change your build.gradle:
Add the plugin repo and the gradle plugin:
buildscript {
repositories {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
// The Fabric Gradle plugin uses an open ended version to react
// quickly to Android tooling updates
classpath 'io.fabric.tools:gradle:1.+'
}
}
Apply the plugin:
apply plugin: 'com.android.application'
//Put Fabric plugin after Android plugin
apply plugin: 'io.fabric'
Add the Fabric repo:
repositories {
maven { url 'https://maven.fabric.io/public' }
}
Add the Crashlyrics Kit:
dependencies {
compile('com.crashlytics.sdk.android:crashlytics:2.6.6@aar') {
transitive = true;
}
}
Step 2: Add Your API Key and the INTERNET permission in AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
... >
<meta-data
android:name="io.fabric.ApiKey"
GoalKicker.com – Android™ Notes for Professionals 724
android:value="25eeca3bb31cd41577e097cabd1ab9eee9da151d"
/>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
Step 3: Init the Kit at runtime in you code, for example:
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Init the KIT
Fabric.with(this, new Crashlytics());
setContentView(R.layout.activity_main);
}
}
Step 4: Build project. To build and run:
Using the Fabric IDE plugin
Kits can be installed using the Fabric IDE plugin for Android Studio or IntelliJ following this link.
GoalKicker.com – Android™ Notes for Professionals 725
After installing the plugin, restart Android Studio and login with your account using Android Studio.
( short key > CTRL + L)
GoalKicker.com – Android™ Notes for Professionals 726
Then it will show the projects that you have / the project you opened, select the one you need and click next .. next.
Select the kit you would like to add, for his example it is Crashlytics :
GoalKicker.com – Android™ Notes for Professionals 727
Then hit Install. You don't need to add it manually this time like above gradle plugin, instead it will build for you.
GoalKicker.com – Android™ Notes for Professionals 728
Done!
Section 125.2: Capture crashes using Sherlock
Sherlock captures all your crashes and reports them as a notification. When you tap on the notification, it opens up
an activity with all the crash details along with Device and Application info
How to integrate Sherlock with your application?
You just need to add Sherlock as a gradle dependency in your project.
dependencies {
compile('com.github.ajitsing:sherlock:1.0.1@aar') {
transitive = true
}
}
After syncing your android studio, initialize Sherlock in your Application class.
package com.singhajit.login;
GoalKicker.com – Android™ Notes for Professionals 729
import android.app.Application;
import com.singhajit.sherlock.core.Sherlock;
public class SampleApp extends Application {
@Override
public void onCreate() {
super.onCreate();
Sherlock.init(this);
}
}
Thats all you need to do. Also Sherlock does much more than just reporting a crash. To checkout all its features
take a look at this article.
Demo
Section 125.3: Force a Test Crash With Fabric
Add a button you can tap to trigger a crash. Paste this code into your layout where you’d like the button to appear.
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Force Crash!"
android:onClick="forceCrash"
android:layout_centerVertical="true"
GoalKicker.com – Android™ Notes for Professionals 730
android:layout_centerHorizontal="true" />
Throw a RuntimeException
public void forceCrash(View view) {
throw new RuntimeException("This is a crash");
}
Run your app and tap the new button to cause a crash. In a minute or two you should be able to see the crash on
your Crashlytics dashboard as well as you will get a mail.
Section 125.4: Crash Reporting with ACRA
Step 1: Add the dependency of latest ACRA AAR to your application gradle(build.gradle).
Step 2: In your application class(the class which extends Application; if not create it) Add a @ReportsCrashes
annotation and override the attachBaseContext() method.
Step 3: Initialize the ACRA class in your application class
@ReportsCrashes(
formUri = "Your choice of backend",
reportType = REPORT_TYPES(JSON/FORM),
httpMethod = HTTP_METHOD(POST/PUT),
formUriBasicAuthLogin = "AUTH_USERNAME",
formUriBasicAuthPassword = "AUTH_PASSWORD,
customReportContent = {
ReportField.USER_APP_START_DATE,
ReportField.USER_CRASH_DATE,
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.ANDROID_VERSION,
ReportField.DEVICE_ID,
ReportField.BUILD,
ReportField.BRAND,
ReportField.DEVICE_FEATURES,
ReportField.PACKAGE_NAME,
ReportField.REPORT_ID,
ReportField.STACK_TRACE,
},
mode = NOTIFICATION_TYPE(TOAST,DIALOG,NOTIFICATION)
resToastText = R.string.crash_text_toast)
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Initialization of ACRA
ACRA.init(this);
}
}
Where AUTH_USERNAME and AUTH_PASSWORD are the credentials of your desired backends.
Step 4: Define the Application class in AndroidManifest.xml
<application
android:name=".MyApplication">
<service></service>
GoalKicker.com – Android™ Notes for Professionals 731
<activity></activity>
<receiver></receiver>
</application>
Step 5: Make sure you have internet permission to receive the report from crashed application
<uses-permission android:name="android.permission.INTERNET"/>
In case if you want to send the silent report to the backend then just use the below method to achieve it.
ACRA.getErrorReporter().handleSilentException(e);
GoalKicker.com – Android™ Notes for Professionals 732
Chapter 126: Check Internet Connectivity
Parameter Detail
Context A reference of Activity context
This method is used to check weather WI-Fi is connected or not.
Section 126.1: Check if device has internet connectivity
Add the required network permissions to the application manifest file:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
/**
* If network connectivity is available, will return true
*
* @param context the current context
* @return boolean true if a network connection is available
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) {
Log.d("NetworkCheck", "isNetworkAvailable: No");
return false;
}
// get network info for all of the data interfaces (e.g. WiFi, 3G, LTE, etc.)
NetworkInfo[] info = connectivity.getAllNetworkInfo();
// make sure that there is at least one interface to test against
if (info != null) {
// iterate through the interfaces
for (int i = 0; i < info.length; i++) {
// check this interface for a connected state
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
Log.d("NetworkCheck", "isNetworkAvailable: Yes");
return true;
}
}
}
return false;
}
Section 126.2: How to check network strength in android?
ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo Info = cm.getActiveNetworkInfo();
if (Info == null || !Info.isConnectedOrConnecting()) {
Log.i(TAG, "No connection");
} else {
int netType = Info.getType();
int netSubtype = Info.getSubtype();
if (netType == ConnectivityManager.TYPE_WIFI) {
Log.i(TAG, "Wifi connection");
GoalKicker.com – Android™ Notes for Professionals 733
WifiManager wifiManager = (WifiManager)
getApplication().getSystemService(Context.WIFI_SERVICE);
List<ScanResult> scanResult = wifiManager.getScanResults();
for (int i = 0; i < scanResult.size(); i++) {
Log.d("scanResult", "Speed of wifi"+scanResult.get(i).level);//The db level of
signal
}
// Need to get wifi strength
} else if (netType == ConnectivityManager.TYPE_MOBILE) {
Log.i(TAG, "GPRS/3G connection");
// Need to get differentiate between 3G/GPRS
}
}
Section 126.3: How to check network strength
To check exact strength in decibels use this-
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo Info = cm.getActiveNetworkInfo();
if (Info == null || !Info.isConnectedOrConnecting()) {
Log.i(TAG, "No connection");
} else {
int netType = Info.getType();
int netSubtype = Info.getSubtype();
if (netType == ConnectivityManager.TYPE_WIFI) {
Log.i(TAG, "Wifi connection");
WifiManager wifiManager = (WifiManager)
getApplication().getSystemService(Context.WIFI_SERVICE);
List<ScanResult> scanResult = wifiManager.getScanResults();
for (int i = 0; i < scanResult.size(); i++) {
Log.d("scanResult", "Speed of wifi"+scanResult.get(i).level);//The db level of
signal
}
// Need to get wifi strength
} else if (netType == ConnectivityManager.TYPE_MOBILE) {
Log.i(TAG, "GPRS/3G connection");
// Need to get differentiate between 3G/GPRS
}
}
To check Network type use this Classpublic
class Connectivity {
/*
* These constants aren't yet available in my API level (7), but I need to
* handle these cases if they come up, on newer versions
*/
public static final int NETWORK_TYPE_EHRPD = 14; // Level 11
public static final int NETWORK_TYPE_EVDO_B = 12; // Level 9
public static final int NETWORK_TYPE_HSPAP = 15; // Level 13
public static final int NETWORK_TYPE_IDEN = 11; // Level 8
public static final int NETWORK_TYPE_LTE = 13; // Level 11
/**
GoalKicker.com – Android™ Notes for Professionals 734
* Check if there is any connectivity
*
* @param context
* @return
*/
public static boolean isConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
return (info != null && info.isConnected());
}
/**
* Check if there is fast connectivity
*
* @param context
* @return
*/
public static String isConnectedFast(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if ((info != null && info.isConnected())) {
return Connectivity.isConnectionFast(info.getType(),
info.getSubtype());
} else
return "No NetWork Access";
}
/**
* Check if the connection is fast
*
* @param type
* @param subType
* @return
*/
public static String isConnectionFast(int type, int subType) {
if (type == ConnectivityManager.TYPE_WIFI) {
System.out.println("CONNECTED VIA WIFI");
return "CONNECTED VIA WIFI";
} else if (type == ConnectivityManager.TYPE_MOBILE) {
switch (subType) {
case TelephonyManager.NETWORK_TYPE_1xRTT:
return "NETWORK TYPE 1xRTT"; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_CDMA:
return "NETWORK TYPE CDMA (3G) Speed: 2 Mbps"; // ~ 14-64 kbps
case TelephonyManager.NETWORK_TYPE_EDGE:
return "NETWORK TYPE EDGE (2.75G) Speed: 100-120 Kbps"; // ~
// 50-100
// kbps
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return "NETWORK TYPE EVDO_0"; // ~ 400-1000 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return "NETWORK TYPE EVDO_A"; // ~ 600-1400 kbps
case TelephonyManager.NETWORK_TYPE_GPRS:
return "NETWORK TYPE GPRS (2.5G) Speed: 40-50 Kbps"; // ~ 100
// kbps
case TelephonyManager.NETWORK_TYPE_HSDPA:
return "NETWORK TYPE HSDPA (4G) Speed: 2-14 Mbps"; // ~ 2-14
GoalKicker.com – Android™ Notes for Professionals 735
// Mbps
case TelephonyManager.NETWORK_TYPE_HSPA:
return "NETWORK TYPE HSPA (4G) Speed: 0.7-1.7 Mbps"; // ~
// 700-1700
// kbps
case TelephonyManager.NETWORK_TYPE_HSUPA:
return "NETWORK TYPE HSUPA (3G) Speed: 1-23 Mbps"; // ~ 1-23
// Mbps
case TelephonyManager.NETWORK_TYPE_UMTS:
return "NETWORK TYPE UMTS (3G) Speed: 0.4-7 Mbps"; // ~ 400-7000
// kbps
// NOT AVAILABLE YET IN API LEVEL 7
case Connectivity.NETWORK_TYPE_EHRPD:
return "NETWORK TYPE EHRPD"; // ~ 1-2 Mbps
case Connectivity.NETWORK_TYPE_EVDO_B:
return "NETWORK_TYPE_EVDO_B"; // ~ 5 Mbps
case Connectivity.NETWORK_TYPE_HSPAP:
return "NETWORK TYPE HSPA+ (4G) Speed: 10-20 Mbps"; // ~ 10-20
// Mbps
case Connectivity.NETWORK_TYPE_IDEN:
return "NETWORK TYPE IDEN"; // ~25 kbps
case Connectivity.NETWORK_TYPE_LTE:
return "NETWORK TYPE LTE (4G) Speed: 10+ Mbps"; // ~ 10+ Mbps
// Unknown
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
return "NETWORK TYPE UNKNOWN";
default:
return "";
}
} else {
return "";
}
}
}
GoalKicker.com – Android™ Notes for Professionals 736
Chapter 127: Creating your own libraries
for Android applications
Section 127.1: Create a library available on Jitpack.io
Perform the following steps to create the library:
1. Create a GitHub account.
2. Create a Git repository containing your library project.
3. Modify your library project's build.gradle file by adding the following code:
apply plugin: 'com.github.dcendents.android-maven'
...
// Build a jar with source files.
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.sourceFiles
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.compile
}
// Build a jar with javadoc.
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives sourcesJar
archives javadocJar
}
Make sure that you commit/push the above changes to GitHub.
4. Create a release from the current code on Github.
5. Run gradlew install on your code.
6. Your library is now available by the following dependency:
compile 'com.github.[YourUser]:[github repository name]:[release tag]'
Section 127.2: Creating library project
To create a libary , you should use File -> New -> New Module -> Android Library. This will create a basic
library project.
GoalKicker.com – Android™ Notes for Professionals 737
When that's done, you must have a project that is set up the following manner:
[project root directory]
[library root directory]
[gradle]
build.gradle //project level
gradle.properties
gradlew
gradlew.bat
local.properties
settings.gradle //this is important!
Your settings.gradle file must contain the following:
include ':[library root directory]'
Your [library root directory] must contain the following:
[libs]
[src]
[main]
[java]
[library package]
[test]
[java]
[library package]
build.gradle //"app"-level
proguard-rules.pro
Your "app"-level build.gradle file must contain the following:
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
}
}
With that, your project should be working fine!
Section 127.3: Using library in project as a module
To use the library, you must include it as a dependency with the following line:
compile project(':[library root directory]')
GoalKicker.com – Android™ Notes for Professionals 738
Chapter 128: Device Display Metrics
Section 128.1: Get the screens pixel dimensions
To retrieve the screens width and height in pixels, we can make use of the WindowManagers display metrics.
// Get display metrics
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
These DisplayMetrics hold a series of information about the devices screen, like its density or size:
// Get width and height in pixel
Integer heightPixels = metrics.heightPixels;
Integer widthPixels = metrics.widthPixels;
Section 128.2: Get screen density
To get the screens density, we also can make use of the Windowmanagers DisplayMetrics. This is a quick example:
// Get density in dpi
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
int densityInDpi = metrics.densityDpi;
Section 128.3: Formula px to dp, dp to px conversation
DP to Pixel:
private int dpToPx(int dp)
{
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
Pixel to DP:
private int pxToDp(int px)
{
return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}
GoalKicker.com – Android™ Notes for Professionals 739
Chapter 129: Building Backwards
Compatible Apps
Section 129.1: How to handle deprecated API
It is unlikely for a developer to not come across a deprecated API during a development process. A deprecated
program element is one that programmers are discouraged from using, typically because it is dangerous, or
because a better alternative exists. Compilers and analyzers (like LINT) warn when a deprecated program element
is used or overridden in non-deprecated code.
A deprecated API is usually identified in Android Studio using a strikeout. In the example below, the method
.getColor(int id) is deprecated:
getResources().getColor(R.color.colorAccent));
If possible, developers are encouraged to use alternative APIs and elements. It is possible to check backwards
compatibility of a library by visiting the Android documentation for the library and checking the "Added in API level
x" section:
In the case that the API you need to use is not compatible with the Android version that your users are using, you
should check for the API level of the user before using that library. For example:
//Checks the API level of the running device
if (Build.VERSION.SDK_INT < 23) {
//use for backwards compatibility with API levels below 23
int color = getResources().getColor(R.color.colorPrimary);
} else {
int color = getResources().getColor(R.color.colorPrimary, getActivity().getTheme());
}
Using this method ensures that your app will remain compatible with new Android versions as well as existing
versions.
GoalKicker.com – Android™ Notes for Professionals 740
Easier alternative: Use the Support Library
If the Support Libraries are used, often there are static helper methods to accomplish the same task with less client
code. Instead of the if/else block above, just use:
final int color = android.support.v4.content.ContextCompat
.getColor(context, R.color.colorPrimary);
Most deprecated methods that have newer methods with a different signature and many new features that may
not have been able to be used on older versions have compatibility helper methods like this. To find others, browse
through the support library for classes like ContextCompat, ViewCompat, etc.
GoalKicker.com – Android™ Notes for Professionals 741
Chapter 130: Loader
Class Description
LoaderManager An abstract class associated with an Activity or Fragment for managing one or
more Loader instances.
LoaderManager.LoaderCallbacks A callback interface for a client to interact with the LoaderManager.
Loader An abstract class that performs asynchronous loading of data.
AsyncTaskLoader Abstract loader that provides an AsyncTask to do the work.
CursorLoader A subclass of AsyncTaskLoader that queries the ContentResolver and returns a
Cursor.
Loader is good choice for prevent memory leak if you want to load data in background when oncreate method is
called. For example when we execute Asynctask in oncreate method and we rotate the screen so the activity will
recreate which will execute another AsyncTask again, so probably two Asyntask running in parallel together rather
than like loader which will continue the background process we executed before.
Section 130.1: Basic AsyncTaskLoader
AsyncTaskLoader is an abstract Loader that provides an AsyncTask to do the work.
Here some basic implementation:
final class BasicLoader extends AsyncTaskLoader<String> {
public BasicLoader(Context context) {
super(context);
}
@Override
public String loadInBackground() {
// Some work, e.g. load something from internet
return "OK";
}
@Override
public void deliverResult(String data) {
if (isStarted()) {
// Deliver result if loader is currently started
super.deliverResult(data);
}
}
@Override
protected void onStartLoading() {
// Start loading
forceLoad();
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
GoalKicker.com – Android™ Notes for Professionals 742
// Ensure the loader is stopped
onStopLoading();
}
}
Typically Loader is initialized within the activity's onCreate() method, or within the fragment's
onActivityCreated(). Also usually activity or fragment implements LoaderManager.LoaderCallbacks interface:
public class MainActivity extends Activity implements LoaderManager.LoaderCallbacks<String> {
// Unique id for loader
private static final int LDR_BASIC_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize loader; Some data can be passed as second param instead of Bundle.Empty
getLoaderManager().initLoader(LDR_BASIC_ID, Bundle.EMPTY, this);
}
@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
return new BasicLoader(this);
}
@Override
public void onLoadFinished(Loader<String> loader, String data) {
Toast.makeText(this, data, Toast.LENGTH_LONG).show();
}
@Override
public void onLoaderReset(Loader<String> loader) {
}
}
In this example, when loader completed, toast with result will be shown.
Section 130.2: AsyncTaskLoader with cache
It's a good practice to cache loaded result to avoid multiple loading of same data.
To invalidate cache onContentChanged() should be called. If loader has been already started, forceLoad() will be
called, otherwise (if loader in stopped state) loader will be able to understand content change with
takeContentChanged() check.
Remark: onContentChanged() must be called from the process's main thread.
Javadocs says about takeContentChanged():
Take the current flag indicating whether the loader's content had changed while it was stopped. If it had,
true is returned and the flag is cleared.
public abstract class BaseLoader<T> extends AsyncTaskLoader<T> {
// Cached result saved here
private final AtomicReference<T> cache = new AtomicReference<>();
GoalKicker.com – Android™ Notes for Professionals 743
public BaseLoader(@NonNull final Context context) {
super(context);
}
@Override
public final void deliverResult(final T data) {
if (!isReset()) {
// Save loaded result
cache.set(data);
if (isStarted()) {
super.deliverResult(data);
}
}
}
@Override
protected final void onStartLoading() {
// Register observers
registerObserver();
final T cached = cache.get();
// Start new loading if content changed in background
// or if we never loaded any data
if (takeContentChanged() || cached == null) {
forceLoad();
} else {
deliverResult(cached);
}
}
@Override
public final void onStopLoading() {
cancelLoad();
}
@Override
protected final void onReset() {
super.onReset();
onStopLoading();
// Clear cache and remove observers
cache.set(null);
unregisterObserver();
}
/* virtual */
protected void registerObserver() {
// Register observers here, call onContentChanged() to invalidate cache
}
/* virtual */
protected void unregisterObserver() {
// Remove observers
}
}
Section 130.3: Reloading
To invalidate your old data and restart existing loader you can use restartLoader() method:
private void reload() {
getLoaderManager().reastartLoader(LOADER_ID, Bundle.EMPTY, this);
GoalKicker.com – Android™ Notes for Professionals 744
}
Section 130.4: Pass parameters using a Bundle
You can pass parameters by Bundle:
Bundle myBundle = new Bundle();
myBundle.putString(MY_KEY, myValue);
Get the value in onCreateLoader:
@Override
public Loader<String> onCreateLoader(int id, final Bundle args) {
final String myParam = args.getString(MY_KEY);
...
}
GoalKicker.com – Android™ Notes for Professionals 745
Chapter 131: ProGuard - Obfuscating and
Shrinking your code
Section 131.1: Rules for some of the widely used Libraries
Currently it contains rules for following libraries:
1. ButterKnife
2. RxJava
3. Android Support Library
4. Android Design Support Library
5. Retrofit
6. Gson and Jackson
7. Otto
8. Crashlitycs
9. Picasso
10. Volley
11. OkHttp3
12. Parcelable
#Butterknife
-keep class butterknife.** { *; }
-keepnames class * { @butterknife.Bind *;}
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
# rxjava
-keep class rx.schedulers.Schedulers {
public static <methods>;
}
-keep class rx.schedulers.ImmediateScheduler {
public <methods>;
}
-keep class rx.schedulers.TestScheduler {
public <methods>;
}
-keep class rx.schedulers.Schedulers {
public static ** test();
}
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
long producerNode;
long consumerNode;
}
GoalKicker.com – Android™ Notes for Professionals 746
# Support library
-dontwarn android.support.**
-dontwarn android.support.v4.**
-keep class android.support.v4.** { *; }
-keep interface android.support.v4.** { *; }
-dontwarn android.support.v7.**
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
# support design
-dontwarn android.support.design.**
-keep class android.support.design.** { *; }
-keep interface android.support.design.** { *; }
-keep public class android.support.design.R$* { *; }
# retrofit
-dontwarn okio.**
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-dontwarn com.squareup.okhttp.**
-dontwarn rx.**
-dontwarn retrofit.**
-keep class retrofit.** { *; }
-keepclasseswithmembers class * {
@retrofit.http.* <methods>;
}
-keep class sun.misc.Unsafe { *; }
#your package path where your gson models are stored
-keep class com.abc.model.** { *; }
# Keep these for GSON and Jackson
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes EnclosingMethod
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.** { *; }
#keep otto
-keepattributes *Annotation*
-keepclassmembers class ** {
@com.squareup.otto.Subscribe public *;
@com.squareup.otto.Produce public *;
}
# Crashlitycs 2.+
-keep class com.crashlytics.** { *; }
-keep class com.crashlytics.android.**
-keepattributes SourceFile, LineNumberTable, *Annotation*
# If you are using custom exceptions, add this line so that custom exception types are skipped
during obfuscation:
-keep public class * extends java.lang.Exception
# For Fabric to properly de-obfuscate your crash reports, you need to remove this line from your
ProGuard config:
# -printmapping mapping.txt
# Picasso
-dontwarn com.squareup.okhttp.**
GoalKicker.com – Android™ Notes for Professionals 747
# Volley
-keep class com.android.volley.toolbox.ImageLoader { *; }
# OkHttp3
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# Needed for Parcelable/SafeParcelable Creators to not get stripped
-keepnames class * implements android.os.Parcelable {
public static final ** CREATOR;
}
Section 131.2: Remove trace logging (and other) statements at
build time
If you want to remove calls to certain methods, assuming they return void and have no side affects (as in, calling
them doesn't change any system values, reference arguments, statics, etc.) then you can have ProGuard remove
them from the output after the build is complete.
For example, I find this useful in removing debug/verbose logging statements useful in debugging, but generating
the strings for them is unnecessary in production.
# Remove the debug and verbose level Logging statements.
# That means the code to generate the arguments to these methods will also not be called.
# ONLY WORKS IF -dontoptimize IS _NOT_ USED in any ProGuard configs
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
}
Note: If -dontoptimize is used in any ProGuard config so that it is not minifying/removing unused code, then this
will not strip out the statements. (But who would not want to remove unused code, right?)
Note2: this call will remove the call to log, but will not protect you code. The Strings will actually remain in the
generated apk. Read more in this post.
Section 131.3: Protecting your code from hackers
Obfuscation is often considered as a magic solution for code protection, by making your code harder to understand
if it ever gets de-compiled by hackers.
But if you're thinking that removing the Log.x(..) actually removes the information the hackers need, you'll have a
nasty surprise.
Removing all your log calls with:
-assumenosideeffects class android.util.Log {
public static *** d(...);
...etc
}
will indeed remove the Log call itself, but usually not the Strings you put into them.
If for example inside your log call you type a common log message such as: Log.d(MyTag,"Score="+score);, the
compiler converts the + to a 'new StringBuilder()' outside the Log call. ProGuard doesn't change this new object.
GoalKicker.com – Android™ Notes for Professionals 748
Your de-compiled code will still have a hanging StringBuilder for "Score=", appended with the obfuscated version
for score variable (let's say it was converted to b).
Now the hacker knows what is b, and make sense of your code.
A good practice to actually remove these residuals from your code is either not put them there in the first place
(Use String formatter instead, with proguard rules to remove them), or to wrap your Log calls with:
if (BuildConfig.DEBUG) {
Log.d(TAG,".."+var);
}
Tip:
Test how well protected your obfuscated code is by de-compiling it yourself!
1. dex2jar - converts the apk to jar
2. jd - decompiles the jar and opens it in a gui editor
Section 131.4: Enable ProGuard for your build
For enabling ProGuard configurations for your application you need to enable it in your module level gradle file. you
need to set the value of minifyEnabled true.
You can also enable shrinkResources true which will remove resources that ProGuard flaggs as unused.
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
The above code will apply your ProGuard configurations contained in proguard-rules.pro ("proguard-project.txt"
in Eclipse) to your released apk.
To enable you to later determine the line on which an exception occurred in a stack trace, "proguard-rules.pro"
should contain following lines:
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
To enable Proguard in Eclipse add proguard.config=${sdk.dir}/tools/proguard/proguardandroid.
txt:proguard-project.txt to "project.properties"
Section 131.5: Enabling ProGuard with a custom obfuscation
configuration file
ProGuard allows the developer to obfuscate, shrink and optimize his code.
#1 The first step of the procedure is to enable proguard on the build.
This can be done by setting the 'minifyEnabled' command to true on your desired build
#2 The second step is to specify which proguard files are we using for the given build
GoalKicker.com – Android™ Notes for Professionals 749
This can be done by setting the 'proguardFiles' line with the proper filenames
buildTypes {
debug {
minifyEnabled false
}
testRelease {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rulestests.
pro'
}
productionRelease {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rulestests.
pro', 'proguard-rules-release.pro'
}
}
#3 The developer can then edit his proguard file with the rules he desires.
That can be done by editting the file (for example 'proguard-rules-tests.pro') and adding the desired constraints.
The following file serves as an example proguard file
// default & basic optimization configurations
-optimizationpasses 5
-dontpreverify
-repackageclasses ''
-allowaccessmodification
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*
-verbose
-dump obfuscation/class_files.txt
-printseeds obfuscation/seeds.txt
-printusage obfuscation/unused.txt // unused classes that are stripped out in the process
-printmapping obfuscation/mapping.txt // mapping file that shows the obfuscated names of the classes
after proguad is applied
// the developer can specify keywords for the obfuscation (I myself use fruits for obfuscation names
once in a while :-) )
-obfuscationdictionary obfuscation/keywords.txt
-classobfuscationdictionary obfuscation/keywords.txt
-packageobfuscationdictionary obfuscation/keywords.txt
Finally, whenever the developer runs and/or generates his new .APK file, the custom proguard configurations will
be applied thus fulfilling the requirements.
GoalKicker.com – Android™ Notes for Professionals 750
Chapter 132: Typedef Annotations:
@IntDef, @StringDef
Section 132.1: IntDef Annotations
This annotation ensures that only the valid integer constants that you expect are used.
The following example illustrates the steps to create an annotation:
import android.support.annotation.IntDef;
public abstract class Car {
//Define the list of accepted constants
@IntDef({MICROCAR, CONVERTIBLE, SUPERCAR, MINIVAN, SUV})
//Tell the compiler not to store annotation data in the .class file
@Retention(RetentionPolicy.SOURCE)
//Declare the CarType annotation
public @interface CarType {}
//Declare the constants
public static final int MICROCAR = 0;
public static final int CONVERTIBLE = 1;
public static final int SUPERCAR = 2;
public static final int MINIVAN = 3;
public static final int SUV = 4;
@CarType
private int mType;
@CarType
public int getCarType(){
return mType;
};
public void setCarType(@CarType int type){
mType = type;
}
}
They also enable code completion to automatically offer the allowed constants.
When you build this code, a warning is generated if the type parameter does not reference one of the defined
constants.
Section 132.2: Combining constants with flags
Using the IntDef#flag() attribute set to true, multiple constants can be combined.
Using the same example in this topic:
public abstract class Car {
//Define the list of accepted constants
@IntDef(flag=true, value={MICROCAR, CONVERTIBLE, SUPERCAR, MINIVAN, SUV})
//Tell the compiler not to store annotation data in the .class file
@Retention(RetentionPolicy.SOURCE)
GoalKicker.com – Android™ Notes for Professionals 751
.....
}
Users can combine the allowed constants with a flag (such as |, &, ^ ).
GoalKicker.com – Android™ Notes for Professionals 752
Chapter 133: Capturing Screenshots
Section 133.1: Taking a screenshot of a particular view
If you want to take a screenshot of a particular View v, then you can use the following code:
Bitmap viewBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.RGB_565);
Canvas viewCanvas = new Canvas(viewBitmap);
Drawable backgroundDrawable = v.getBackground();
if(backgroundDrawable != null){
// Draw the background onto the canvas.
backgroundDrawable.draw(viewCanvas);
}
else{
viewCanvas.drawColor(Color.GREEN);
// Draw the view onto the canvas.
v.draw(viewCanvas)
}
// Write the bitmap generated above into a file.
String fileStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
OutputStream outputStream = null;
try{
imgFile = new
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), fileStamp +
".png");
outputStream = new FileOutputStream(imgFile);
viewBitmap.compress(Bitmap.CompressFormat.PNG, 40, outputStream);
outputStream.close();
}
catch(Exception e){
e.printStackTrace();
}
Section 133.2: Capturing Screenshot via Android Studio
1. Open Android Monitor Tab
2. Click on Screen Capture Button
GoalKicker.com – Android™ Notes for Professionals 753
Section 133.3: Capturing Screenshot via ADB and saving
directly in your PC
If you use Linux (or Windows with Cygwin), you can run:
adb shell screencap -p | sed 's/\r$//' > screenshot.png
Section 133.4: Capturing Screenshot via Android Device
Monitor
1. Open Android Device Monitor ( ie C:<ANDROID_SDK_LOCATION>\tools\monitor.bat)
2. Select your device
3. Click on Screen Capture Button
GoalKicker.com – Android™ Notes for Professionals 754
Section 133.5: Capturing Screenshot via ADB
Example below saves a screenshot on Devices's Internal Storage.
adb shell screencap /sdcard/screen.png
GoalKicker.com – Android™ Notes for Professionals 755
Chapter 134: MVP Architecture
This topic will provide Model‑View‑Presenter (MVP) architecture of Android with various examples.
Section 134.1: Login example in the Model View Presenter
(MVP) pattern
Let's see MVP in action using a simple Login Screen. There are two Buttons—one for login action and another for a
registration screen; two EditTexts—one for the email and the other for the password.
LoginFragment (The View)
public class LoginFragment extends Fragment implements LoginContract.PresenterToView,
View.OnClickListener {
private View view;
private EditText email, password;
private Button login, register;
private LoginContract.ToPresenter presenter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
return inflater.inflate(R.layout.fragment_login, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
email = (EditText) view.findViewById(R.id.email_et);
password = (EditText) view.findViewById(R.id.password_et);
login = (Button) view.findViewById(R.id.login_btn);
login.setOnClickListener(this);
register = (Button) view.findViewById(R.id.register_btn);
register.setOnClickListener(this);
presenter = new LoginPresenter(this);
presenter.isLoggedIn();
}
@Override
public void onLoginResponse(boolean isLoginSuccess) {
if (isLoginSuccess) {
startActivity(new Intent(getActivity(), MapActivity.class));
getActivity().finish();
}
}
@Override
public void onError(String message) {
Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
}
@Override
public void isLoggedIn(boolean isLoggedIn) {
if (isLoggedIn) {
GoalKicker.com – Android™ Notes for Professionals 756
startActivity(new Intent(getActivity(), MapActivity.class));
getActivity().finish();
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.login_btn:
LoginItem loginItem = new LoginItem();
loginItem.setPassword(password.getText().toString().trim());
loginItem.setEmail(email.getText().toString().trim());
presenter.login(loginItem);
break;
case R.id.register_btn:
startActivity(new Intent(getActivity(), RegisterActivity.class));
getActivity().finish();
break;
}
}
}
LoginPresenter (The Presenter)
public class LoginPresenter implements LoginContract.ToPresenter {
private LoginContract.PresenterToModel model;
private LoginContract.PresenterToView view;
public LoginPresenter(LoginContract.PresenterToView view) {
this.view = view;
model = new LoginModel(this);
}
@Override
public void login(LoginItem userCredentials) {
model.login(userCredentials);
}
@Override
public void isLoggedIn() {
model.isLoggedIn();
}
@Override
public void onLoginResponse(boolean isLoginSuccess) {
view.onLoginResponse(isLoginSuccess);
}
@Override
public void onError(String message) {
view.onError(message);
}
@Override
public void isloggedIn(boolean isLoggedin) {
view.isLoggedIn(isLoggedin);
}
}
LoginModel (The Model)
GoalKicker.com – Android™ Notes for Professionals 757
public class LoginModel implements LoginContract.PresenterToModel,
ResponseErrorListener.ErrorListener {
private static final String TAG = LoginModel.class.getSimpleName();
private LoginContract.ToPresenter presenter;
public LoginModel(LoginContract.ToPresenter presenter) {
this.presenter = presenter;
}
@Override
public void login(LoginItem userCredentials) {
if (validateData(userCredentials)) {
try {
performLoginOperation(userCredentials);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
presenter.onError(BaseContext.getContext().getString(R.string.error_login_field_validation));
}
}
@Override
public void isLoggedIn() {
DatabaseHelper database = new DatabaseHelper(BaseContext.getContext());
presenter.isloggedIn(database.isLoggedIn());
}
private boolean validateData(LoginItem userCredentials) {
return Patterns.EMAIL_ADDRESS.matcher(userCredentials.getEmail()).matches()
&& !userCredentials.getPassword().trim().equals("");
}
private void performLoginOperation(final LoginItem userCredentials) throws JSONException {
JSONObject postData = new JSONObject();
postData.put(Constants.EMAIL, userCredentials.getEmail());
postData.put(Constants.PASSWORD, userCredentials.getPassword());
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, Url.AUTH, postData,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
String token = response.getString(Constants.ACCESS_TOKEN);
DatabaseHelper databaseHelper = new
DatabaseHelper(BaseContext.getContext());
databaseHelper.login(token);
Log.d(TAG, "onResponse: " + token);
} catch (JSONException e) {
e.printStackTrace();
}
presenter.onLoginResponse(true);
}
}, new ErrorResponse(this));
RequestQueue queue = Volley.newRequestQueue(BaseContext.getContext());
queue.add(request);
}
GoalKicker.com – Android™ Notes for Professionals 758
@Override
public void onError(String message) {
presenter.onError(message);
}
}
Class Diagram
Let's see the action in the form of class diagram.
Notes:
This example uses Volley for network communication, but this library is not required for MVP
UrlUtils is a class which contains all the links for my API Endpoints
ResponseErrorListener.ErrorListener is an interface that listens for error in ErrorResponse that
implements Volley's Response.ErrorListener; these classes are not included here as they are not directly
part of this example
Section 134.2: Simple Login Example in MVP
Required package structure
GoalKicker.com – Android™ Notes for Professionals 759
XML activity_login
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<EditText
android:id="@+id/et_login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="USERNAME" />
<EditText
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="PASSWORD" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
GoalKicker.com – Android™ Notes for Professionals 760
<Button
android:id="@+id/btn_login_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="4dp"
android:layout_weight="1"
android:text="Login" />
<Button
android:id="@+id/btn_login_clear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="Clear" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="correct user: mvp, mvp" />
<ProgressBar
android:id="@+id/progress_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp" />
</LinearLayout>
Activity Class LoginActivity.class
public class LoginActivity extends AppCompatActivity implements ILoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
private ILoginPresenter loginPresenter;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//find view
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
//set listener
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
//init
loginPresenter = new LoginPresenterCompl(this);
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
}
@Override
GoalKicker.com – Android™ Notes for Professionals 761
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
loginPresenter.clear();
break;
case R.id.btn_login_login:
loginPresenter.setProgressBarVisiblity(View.VISIBLE);
btnLogin.setEnabled(false);
btnClear.setEnabled(false);
loginPresenter.doLogin(editUser.getText().toString(),
editPass.getText().toString());
break;
}
}
@Override
public void onClearText() {
editUser.setText("");
editPass.setText("");
}
@Override
public void onLoginResult(Boolean result, int code) {
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
btnLogin.setEnabled(true);
btnClear.setEnabled(true);
if (result){
Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onSetProgressBarVisibility(int visibility) {
progressBar.setVisibility(visibility);
}
}
Creating an ILoginView Interface
Create an ILoginView interface for update info from Presenter under view folder as follows:
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
public void onSetProgressBarVisibility(int visibility);
}
Creating an ILoginPresenter Interface
Create an ILoginPresenter interface in order to communicate with LoginActivity (Views) and create the
LoginPresenterCompl class for handling login functionality and reporting back to the Activity. The
LoginPresenterCompl class implements the ILoginPresenter interface:
ILoginPresenter.class
public interface ILoginPresenter {
GoalKicker.com – Android™ Notes for Professionals 762
void clear();
void doLogin(String name, String passwd);
void setProgressBarVisiblity(int visiblity);
}
LoginPresenterCompl.class
public class LoginPresenterCompl implements ILoginPresenter {
ILoginView iLoginView;
IUser user;
Handler handler;
public LoginPresenterCompl(ILoginView iLoginView) {
this.iLoginView = iLoginView;
initUser();
handler = new Handler(Looper.getMainLooper());
}
@Override
public void clear() {
iLoginView.onClearText();
}
@Override
public void doLogin(String name, String passwd) {
Boolean isLoginSuccess = true;
final int code = user.checkUserValidity(name,passwd);
if (code!=0) isLoginSuccess = false;
final Boolean result = isLoginSuccess;
handler.postDelayed(new Runnable() {
@Override
public void run() {
iLoginView.onLoginResult(result, code);
}
}, 5000);
}
@Override
public void setProgressBarVisiblity(int visiblity){
iLoginView.onSetProgressBarVisibility(visiblity);
}
private void initUser(){
user = new UserModel("mvp","mvp");
}
}
Creating a UserModel
Create a UserModel which is like a Pojo class for LoginActivity. Create an IUser interface for Pojo validations:
UserModel.class
public class UserModel implements IUser {
String name;
String passwd;
public UserModel(String name, String passwd) {
this.name = name;
this.passwd = passwd;
}
@Override
GoalKicker.com – Android™ Notes for Professionals 763
public String getName() {
return name;
}
@Override
public String getPasswd() {
return passwd;
}
@Override
public int checkUserValidity(String name, String passwd){
if (name==null||passwd==null||!name.equals(getName())||!passwd.equals(getPasswd())){
return -1;
}
return 0;
}
IUser.class
public interface IUser {
String getName();
String getPasswd();
int checkUserValidity(String name, String passwd);
}
MVP
A Model-view-presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern. It is used
mostly for building user interfaces and offers the following benefits:
Views are more separated from Models. The Presenter is the mediator between Model and View.
It is easier to create unit tests.
Generally, there is a one-to-one mapping between View and Presenter, with the possibility to use multiple
Presenters for complex Views.
GoalKicker.com – Android™ Notes for Professionals 764
GoalKicker.com – Android™ Notes for Professionals 765
Chapter 135: Orientation Changes
Section 135.1: Saving and Restoring Activity State
As your activity begins to stop, the system calls onSaveInstanceState() so your activity can save state information
with a collection of key-value pairs. The default implementation of this method automatically saves information
about the state of the activity's view hierarchy, such as the text in an EditText widget or the scroll position of a
ListView.
To save additional state information for your activity, you must implement onSaveInstanceState() and add keyvalue
pairs to the Bundle object. For example:
public class MainActivity extends Activity {
static final String SOME_VALUE = "int_value";
static final String SOME_OTHER_VALUE = "string_value";
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
// Save custom values into the bundle
savedInstanceState.putInt(SOME_VALUE, someIntValue);
savedInstanceState.putString(SOME_OTHER_VALUE, someStringValue);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
}
The system will call that method before an Activity is destroyed. Then later the system will call
onRestoreInstanceState where we can restore state from the bundle:
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
someIntValue = savedInstanceState.getInt(SOME_VALUE);
someStringValue = savedInstanceState.getString(SOME_OTHER_VALUE);
}
Instance state can also be restored in the standard Activity#onCreate method but it is convenient to do it in
onRestoreInstanceState which ensures all of the initialization has been done and allows subclasses to decide
whether to use the default implementation. Read this stackoverflow post for details.
Note that onSaveInstanceState and onRestoreInstanceState are not guaranteed to be called together. Android
invokes onSaveInstanceState() when there's a chance the activity might be destroyed. However, there are cases
where onSaveInstanceState is called but the activity is not destroyed and as a result onRestoreInstanceState is
not invoked.
Section 135.2: Retaining Fragments
In many cases, we can avoid problems when an Activity is re-created by simply using fragments. If your views and
state are within a fragment, we can easily have the fragment be retained when the activity is re-created:
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
GoalKicker.com – Android™ Notes for Professionals 766
// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment when activity is re-initialized
setRetainInstance(true);
}
public void setData(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
This approach keeps the fragment from being destroyed during the activity lifecycle. They are instead retained
inside the Fragment Manager. See the Android official docs for more information.
Now you can check to see if the fragment already exists by tag before creating one and the fragment will retain it's
state across configuration changes. See the Handling Runtime Changes guide for more details.
Section 135.3: Manually Managing Configuration Changes
If your application doesn't need to update resources during a specific configuration change and you have a
performance limitation that requires you to avoid the activity restart, then you can declare that your activity
handles the configuration change itself, which prevents the system from restarting your activity.
However, this technique should be considered a last resort when you must avoid restarts due to a configuration
change and is not recommended for most applications. To take this approach, we must add the
android:configChanges node to the activity within the AndroidManifest.xml:
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name">
Now, when one of these configurations change, the activity does not restart but instead receives a call to
onConfigurationChanged():
// Within the activity which receives these changes
// Checks the current device orientation, and toasts accordingly
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
See the Handling the Change docs. For more about which configuration changes you can handle in your activity, see
the android:configChanges documentation and the Configuration class.
GoalKicker.com – Android™ Notes for Professionals 767
Section 135.4: Handling AsyncTask
Problem:
If after the AsyncTask starts there is a screen rotation the owning activity is destroyed and recreated.
When the AsyncTask finishes it wants to update the UI that may not valid anymore.
Solution:
Using Loaders, one can easily overcome the activity destruction/recreation.
Example:
MainActivity:
public class MainActivity extends AppCompatActivity
implements LoaderManager.LoaderCallbacks<Bitmap> {
//Unique id for the loader
private static final int MY_LOADER = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoaderManager loaderManager = getSupportLoaderManager();
if(loaderManager.getLoader(MY_LOADER) == null) {
loaderManager.initLoader(MY_LOADER, null, this).forceLoad();
}
}
@Override
public Loader<Bitmap> onCreateLoader(int id, Bundle args) {
//Create a new instance of your Loader<Bitmap>
MyLoader loader = new MyLoader(MainActivity.this);
return loader;
}
@Override
public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) {
// do something in the parent activity/service
// i.e. display the downloaded image
Log.d("MyAsyncTask", "Received result: ");
}
@Override
public void onLoaderReset(Loader<Bitmap> loader) {
}
}
AsyncTaskLoader:
public class MyLoader extends AsyncTaskLoader<Bitmap> {
private WeakReference<Activity> motherActivity;
public MyLoader(Activity activity) {
super(activity);
//We don't use this, but if you want you can use it, but remember, WeakReference
motherActivity = new WeakReference<>(activity);
GoalKicker.com – Android™ Notes for Professionals 768
}
@Override
public Bitmap loadInBackground() {
// Do work. I.e download an image from internet to be displayed in gui.
// i.e. return the downloaded gui
return result;
}
}
Note:
It is important to use either the v4 compatibility library or not, but do not use part of one and part of the other, as it
will lead to compilation errors. To check you can look at the imports for android.support.v4.content and
android.content (you shouldn't have both).
Section 135.5: Lock Screen's rotation programmatically
It is very common that during development, one may find very useful to lock/unlock the device screen during
specific parts of the code.
For instance, while showing a Dialog with information, the developer might want to lock the screen's rotation to prevent
the dialog from being dismissed and the current activity from being rebuilt to unlock it again when the dialog is dismissed.
Even though we can achieve rotation locking from the manifest by doing :
<activity
android:name=".TheActivity"
android:screenOrientation="portrait"
android:label="@string/app_name" >
</activity>
One can do it programmatically as well by doing the following :
public void lockDeviceRotation(boolean value) {
if (value) {
int currentOrientation = getResources().getConfiguration().orientation;
if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
}
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
}
}
}
And then calling the following, to respectively lock and unlock the device rotation
lockDeviceRotation(true)
and
GoalKicker.com – Android™ Notes for Professionals 769
lockDeviceRotation(false)
Section 135.6: Saving and Restoring Fragment State
Fragments also have a onSaveInstanceState() method which is called when their state needs to be saved:
public class MySimpleFragment extends Fragment {
private int someStateValue;
private final String SOME_VALUE_KEY = "someValueToSave";
// Fires when a configuration change occurs and fragment needs to save state
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(SOME_VALUE_KEY, someStateValue);
super.onSaveInstanceState(outState);
}
}
Then we can pull data out of this saved state in onCreateView:
public class MySimpleFragment extends Fragment {
// ...
// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
View view = inflater.inflate(R.layout.my_simple_fragment, container, false);
if (savedInstanceState != null) {
someStateValue = savedInstanceState.getInt(SOME_VALUE_KEY);
// Do something with value if needed
}
return view;
}
}
For the fragment state to be saved properly, we need to be sure that we aren't unnecessarily recreating the
fragment on configuration changes. This means being careful not to reinitialize existing fragments when they
already exist. Any fragments being initialized in an Activity need to be looked up by tag after a configuration change:
public class ParentActivity extends AppCompatActivity {
private MySimpleFragment fragmentSimple;
private final String SIMPLE_FRAGMENT_TAG = "myfragmenttag";
@Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) { // saved instance state, fragment may exist
// look up the instance that already exists by tag
fragmentSimple = (MySimpleFragment)
getSupportFragmentManager().findFragmentByTag(SIMPLE_FRAGMENT_TAG);
} else if (fragmentSimple == null) {
// only create fragment if they haven't been instantiated already
fragmentSimple = new MySimpleFragment();
}
}
}
This requires us to be careful to include a tag for lookup whenever putting a fragment into the activity within a
GoalKicker.com – Android™ Notes for Professionals 770
transaction:
public class ParentActivity extends AppCompatActivity {
private MySimpleFragment fragmentSimple;
private final String SIMPLE_FRAGMENT_TAG = "myfragmenttag";
@Override
protected void onCreate(Bundle savedInstanceState) {
// ... fragment lookup or instantation from above...
// Always add a tag to a fragment being inserted into container
if (!fragmentSimple.isInLayout()) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragmentSimple, SIMPLE_FRAGMENT_TAG)
.commit();
}
}
}
With this simple pattern, we can properly re-use fragments and restore their state across configuration changes.
GoalKicker.com – Android™ Notes for Professionals 771
Chapter 136: Xposed
Section 136.1: Creating a Xposed Module
Xposed is a framework that allows you to hook method calls of other apps. When you do a modification by
decompiling an APK, you can insert/change commands directly wherever you want. However, you will need to
recompile/sign the APK afterwards and you can only distribute the whole package. With Xposed, you can inject
your own code before or after methods, or replace whole methods completely. Unfortunately, you can only install
Xposed on rooted devices. You should use Xposed whenever you want to manipulate the behavior of other apps or
the core Android system and don't want to go through the hassle of decompiling, recompiling and signing APKs.
First, you create a standard app without an Activity in Android Studio.
Then you have to include the following code in your build.gradle:
repositories {
jcenter();
}
After that you add the following dependencies:
provided 'de.robv.android.xposed:api:82'
provided 'de.robv.android.xposed:api:82:sources'
Now you have to place these tags inside the application tag found in the AndroidManifest.xml so Xposed recognizes
your module:
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="YOUR_MODULE_DESCRIPTION" />
<meta-data
android:name="xposedminversion"
android:value="82" />
NOTE: Always replace 82 with the latest Xposed version.
Section 136.2: Hooking a method
Create a new class implementing IXposedHookLoadPackage and implement the handleLoadPackage method:
public class MultiPatcher implements IXposedHookLoadPackage
{
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws
Throwable
{
}
}
Inside the method, you check loadPackageParam.packageName for the package name of the app you want to hook:
GoalKicker.com – Android™ Notes for Professionals 772
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable
{
if (!loadPackageParam.packageName.equals("other.package.name"))
{
return;
}
}
Now you can hook your method and either manipulate it before it's code is run, or after:
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable
{
if (!loadPackageParam.packageName.equals("other.package.name"))
{
return;
}
XposedHelpers.findAndHookMethod(
"other.package.name",
loadPackageParam.classLoader,
"otherMethodName",
YourFirstParameter.class,
YourSecondParameter.class,
new XC_MethodHook()
{
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable
{
Object[] args = param.args;
args[0] = true;
args[1] = "example string";
args[2] = 1;
Object thisObject = param.thisObject;
// Do something with the instance of the class
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable
{
Object result = param.getResult();
param.setResult(result + "example string");
}
});
}
GoalKicker.com – Android™ Notes for Professionals 773
Chapter 137: PackageManager
Section 137.1: Retrieve application version
public String getAppVersion() throws PackageManager.NameNotFoundException {
PackageManager manager = getApplicationContext().getPackageManager();
PackageInfo info = manager.getPackageInfo(
getApplicationContext().getPackageName(),
0);
return info.versionName;
}
Section 137.2: Version name and version code
To get versionName and versionCode of current build of your application you should query Android's package
manager.
try {
// Reference to Android's package manager
PackageManager packageManager = this.getPackageManager();
// Getting package info of this application
PackageInfo info = packageManager.getPackageInfo(this.getPackageName(), 0);
// Version code
info.versionCode
// Version name
info.versionName
} catch (NameNotFoundException e) {
// Handle the exception
}
Section 137.3: Install time and update time
To get the time at which your app was installed or updated, you should query Android's package manager.
try {
// Reference to Android's package manager
PackageManager packageManager = this.getPackageManager();
// Getting package info of this application
PackageInfo info = packageManager.getPackageInfo(this.getPackageName(), 0);
// Install time. Units are as per currentTimeMillis().
info.firstInstallTime
// Last update time. Units are as per currentTimeMillis().
info.lastUpdateTime
} catch (NameNotFoundException e) {
// Handle the exception
}
GoalKicker.com – Android™ Notes for Professionals 774
Section 137.4: Utility method using PackageManager
Here we can find some useful method using PackageManager,
Below method will help to get the app name using package name
private String getAppNameFromPackage(String packageName, Context context) {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> pkgAppsList = context.getPackageManager()
.queryIntentActivities(mainIntent, 0);
for (ResolveInfo app : pkgAppsList) {
if (app.activityInfo.packageName.equals(packageName)) {
return app.activityInfo.loadLabel(context.getPackageManager()).toString();
}
}
return null;
}
Below method will help to get the app icon using package name,
private Drawable getAppIcon(String packageName, Context context) {
Drawable appIcon = null;
try {
appIcon = context.getPackageManager().getApplicationIcon(packageName);
} catch (PackageManager.NameNotFoundException e) {
}
return appIcon;
}
Below method will help to get the list of installed application.
public static List<ApplicationInfo> getLaunchIntent(PackageManager packageManager) {
List<ApplicationInfo> list =
packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
return list;
}
Note: above method will give the launcher application too.
Below method will help to hide the app icon from the launcher.
public static void hideLockerApp(Context context, boolean hide) {
ComponentName componentName = new ComponentName(context.getApplicationContext(),
SplashActivity.class);
int setting = hide ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
: PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
int current = context.getPackageManager().getComponentEnabledSetting(componentName);
if (current != setting) {
context.getPackageManager().setComponentEnabledSetting(componentName, setting,
PackageManager.DONT_KILL_APP);
GoalKicker.com – Android™ Notes for Professionals 775
}
}
Note: After switch off the device and switch on this icon will come back in the launcher.
GoalKicker.com – Android™ Notes for Professionals 776

Comments

Popular posts from this blog

Android 4?

Android2?