Tuesday, April 29, 2014

A good tutorial for Navigation Drawer

The navigation drawer is a panel that displays the app’s main navigation options on the left edge of the screen. It is hidden most of the time, but is revealed when the user swipes a finger from the left edge of the screen or, while at the top level of the app, the user touches the app icon in the action bar.

With navigation drawer, you can easily develop app with YouTube or Google+ apps like navigation.

It's a very good, must see, tutorial of Creating a Navigation Drawer.


Sunday, April 27, 2014

Set text size and color of PagerTitleStrip on ViewPager

Previous example show how to Implement PagerTitleStrip on ViewPager. In the onCreate() method, we get view of PagerTitleStrip (pagerTitleStrip) without using it. We can set text size and color of the PagerTitleStrip by calling its setTextSize() and setTextColor() methods.

Example:
  PagerTitleStrip pagerTitleStrip = (PagerTitleStrip)findViewById(R.id.titlestrip);
  pagerTitleStrip.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
  pagerTitleStrip.setTextColor(Color.BLUE);

on tablet

on phone

Communication between Android using NFC to send text

This example send text between Android devices using NFC. Modify from last post of sending Uri between Android devices using NFC.


Modify AndroidManifest.xml include <intent-filter> of "android.nfc.action.NDEF_DISCOVERED".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidnfc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.NFC"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidnfc.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
            
        </activity>
    </application>

</manifest>

MainActivity.java
package com.example.androidnfc;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcAdapter.OnNdefPushCompleteCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements 
 CreateNdefMessageCallback, OnNdefPushCompleteCallback{
 
 TextView textInfo;
 EditText textOut;
 
 NfcAdapter nfcAdapter;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textInfo = (TextView)findViewById(R.id.info);
  textOut = (EditText)findViewById(R.id.textout);

  nfcAdapter = NfcAdapter.getDefaultAdapter(this);
  if(nfcAdapter==null){
   Toast.makeText(MainActivity.this, 
    "nfcAdapter==null, no NFC adapter exists", 
    Toast.LENGTH_LONG).show();
  }else{
   Toast.makeText(MainActivity.this, 
     "Set Callback(s)", 
     Toast.LENGTH_LONG).show();
   nfcAdapter.setNdefPushMessageCallback(this, this);
   nfcAdapter.setOnNdefPushCompleteCallback(this, this);
  }
 }

 @Override
 protected void onResume() {
  super.onResume();
  Intent intent = getIntent();
  String action = intent.getAction();
  if(action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)){
   Parcelable[] parcelables = 
    intent.getParcelableArrayExtra(
      NfcAdapter.EXTRA_NDEF_MESSAGES);
   NdefMessage inNdefMessage = (NdefMessage)parcelables[0];
   NdefRecord[] inNdefRecords = inNdefMessage.getRecords();
   NdefRecord NdefRecord_0 = inNdefRecords[0];
   String inMsg = new String(NdefRecord_0.getPayload());
   textInfo.setText(inMsg);
  }
 }

 @Override
 protected void onNewIntent(Intent intent) {
  setIntent(intent);
 }

 @Override
 public void onNdefPushComplete(NfcEvent event) {
  
  final String eventString = "onNdefPushComplete\n" + event.toString();
  runOnUiThread(new Runnable() {
   
   @Override
   public void run() {
    Toast.makeText(getApplicationContext(), 
      eventString, 
      Toast.LENGTH_LONG).show();
   }
  });

 }

 @Override
 public NdefMessage createNdefMessage(NfcEvent event) {
  
  String stringOut = textOut.getText().toString();
  byte[] bytesOut = stringOut.getBytes();
  
  NdefRecord ndefRecordOut = new NdefRecord(
    NdefRecord.TNF_MIME_MEDIA, 
    "text/plain".getBytes(),
                new byte[] {}, 
                bytesOut);

  NdefMessage ndefMessageout = new NdefMessage(ndefRecordOut);
  return ndefMessageout;
 }

}

activity_main.xml
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.androidnfc.MainActivity" >
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <EditText
        android:id="@+id/textout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    
    <TextView
        android:id="@+id/info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

download filesDownload the files.

Saturday, April 26, 2014

AudioRecord and AudioTrack, and to implement voice changer

This example show how to use android.media.AudioRecord and android.media.AudioTrack, to record and playback audio. And also show how to implement voice changer by recording and playing audio in different sampling frequency. (Actually, it is almost same as my old example)


AndroidAudioRecordActivity.java
package com.exercise.AndroidAudioRecord;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;

public class AndroidAudioRecordActivity extends Activity {
 
 String[] freqText = {"11.025 KHz (Lowest)", "16.000 KHz", "22.050 KHz", "44.100 KHz (Highest)"};
 Integer[] freqset = {11025, 16000, 22050, 44100};
 private ArrayAdapter<String> adapter;
 
 Spinner spFrequency;
 Button startRec, stopRec, playBack;
 
 Boolean recording;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        startRec = (Button)findViewById(R.id.startrec);
        stopRec = (Button)findViewById(R.id.stoprec);
        playBack = (Button)findViewById(R.id.playback);
        
        startRec.setOnClickListener(startRecOnClickListener);
        stopRec.setOnClickListener(stopRecOnClickListener);
        playBack.setOnClickListener(playBackOnClickListener);
        
        spFrequency = (Spinner)findViewById(R.id.frequency);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, freqText);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spFrequency.setAdapter(adapter);

        stopRec.setEnabled(false);
    }
    
    OnClickListener startRecOnClickListener
    = new OnClickListener(){

  @Override
  public void onClick(View arg0) {
   
   Thread recordThread = new Thread(new Runnable(){

    @Override
    public void run() {
     recording = true;
     startRecord();
    }
    
   });
   
   recordThread.start();
   startRec.setEnabled(false);
   stopRec.setEnabled(true);

  }};
  
 OnClickListener stopRecOnClickListener
 = new OnClickListener(){
  
  @Override
  public void onClick(View arg0) {
   recording = false;
   startRec.setEnabled(true);
   stopRec.setEnabled(false);
  }};
  
 OnClickListener playBackOnClickListener
     = new OnClickListener(){

   @Override
   public void onClick(View v) {
    playRecord();
   }
  
 };
  
 private void startRecord(){

  File file = new File(Environment.getExternalStorageDirectory(), "test.pcm"); 
  
  int selectedPos = spFrequency.getSelectedItemPosition();
  int sampleFreq = freqset[selectedPos];
  
  final String promptStartRecord = 
    "startRecord()\n"
    + file.getAbsolutePath() + "\n"
    + (String)spFrequency.getSelectedItem();
  
  runOnUiThread(new Runnable(){

   @Override
   public void run() {
    Toast.makeText(AndroidAudioRecordActivity.this, 
      promptStartRecord, 
      Toast.LENGTH_LONG).show();
   }});
    
  try {
   file.createNewFile();
   
   OutputStream outputStream = new FileOutputStream(file);
   BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
   DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream);
   
   int minBufferSize = AudioRecord.getMinBufferSize(sampleFreq, 
     AudioFormat.CHANNEL_CONFIGURATION_MONO, 
     AudioFormat.ENCODING_PCM_16BIT);
   
   short[] audioData = new short[minBufferSize];
   
   AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
     sampleFreq,
     AudioFormat.CHANNEL_CONFIGURATION_MONO,
     AudioFormat.ENCODING_PCM_16BIT,
     minBufferSize);
   
   audioRecord.startRecording();
   
   while(recording){
    int numberOfShort = audioRecord.read(audioData, 0, minBufferSize);
    for(int i = 0; i < numberOfShort; i++){
     dataOutputStream.writeShort(audioData[i]);
    }
   }
   
   audioRecord.stop();
   dataOutputStream.close();
   
  } catch (IOException e) {
   e.printStackTrace();
  }

 }

 void playRecord(){
  
  File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");
  
        int shortSizeInBytes = Short.SIZE/Byte.SIZE;
  
  int bufferSizeInBytes = (int)(file.length()/shortSizeInBytes);
  short[] audioData = new short[bufferSizeInBytes];
  
  try {
   InputStream inputStream = new FileInputStream(file);
   BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
   DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
   
   int i = 0;
   while(dataInputStream.available() > 0){
    audioData[i] = dataInputStream.readShort();
    i++;
   }
   
   dataInputStream.close();
   
   int selectedPos = spFrequency.getSelectedItemPosition();
   int sampleFreq = freqset[selectedPos];

   final String promptPlayRecord = 
     "PlayRecord()\n"
     + file.getAbsolutePath() + "\n"
     + (String)spFrequency.getSelectedItem();
   
   Toast.makeText(AndroidAudioRecordActivity.this, 
     promptPlayRecord, 
     Toast.LENGTH_LONG).show();
   
   AudioTrack audioTrack = new AudioTrack(
     AudioManager.STREAM_MUSIC,
     sampleFreq,
     AudioFormat.CHANNEL_CONFIGURATION_MONO,
     AudioFormat.ENCODING_PCM_16BIT,
     bufferSizeInBytes,
     AudioTrack.MODE_STREAM);
   
   audioTrack.play();
   audioTrack.write(audioData, 0, bufferSizeInBytes);

   
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
 
}

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Sampling Frequency" />
    <Spinner
        android:id = "@+id/frequency"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/startrec"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Start Recording" />
    <Button
        android:id="@+id/stoprec"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Stop Recording" />
    <Button
        android:id="@+id/playback"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Play Back" />

</LinearLayout>

Permission of "android.permission.RECORD_AUDIO" and "android.permission.WRITE_EXTERNAL_STORAGE" are needed in AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exercise.AndroidAudioRecord"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AndroidAudioRecordActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

download filesDownload the files.

Download and try the APK HERE.

Run multi AsyncTask at the same time

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.

If you truly want parallel execution, you can invoke executeOnExecutor(java.util.concurrent.Executor, Object[]) with THREAD_POOL_EXECUTOR.

~ reference: AsyncTask | Android Developers



This example show how to execute multi AsyncTask at the same in parallel, by calling executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) for (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB), in our StartAsyncTaskInParallel() method. The first three ProgressBars updated by AsyncTask execute in normal approach by calling execute(), the last two ProgressBar updated by AsyncTask execute in parallel.


MainActivity.java
package com.example.androidparallelasynctask;

import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;

public class MainActivity extends Activity {

 public class MyAsyncTask extends AsyncTask<Void, Integer, Void> {

  ProgressBar myProgressBar;
  
  public MyAsyncTask(ProgressBar target) {
   myProgressBar = target;
  }

  @Override
  protected Void doInBackground(Void... params) {
   for(int i=0; i<100; i++){
    publishProgress(i);
    SystemClock.sleep(100);
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(Integer... values) {
   myProgressBar.setProgress(values[0]);
  }

 }
 
 Button buttonStart;
 ProgressBar progressBar1, progressBar2, progressBar3, progressBar4, progressBar5;
 MyAsyncTask asyncTask1, asyncTask2, asyncTask3, asyncTask4, asyncTask5;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  progressBar1 = (ProgressBar)findViewById(R.id.progressbar1);
  progressBar2 = (ProgressBar)findViewById(R.id.progressbar2);
  progressBar3 = (ProgressBar)findViewById(R.id.progressbar3);
  progressBar4 = (ProgressBar)findViewById(R.id.progressbar4);
  progressBar5 = (ProgressBar)findViewById(R.id.progressbar5);
  
  buttonStart = (Button)findViewById(R.id.start);
  buttonStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    asyncTask1 = new MyAsyncTask(progressBar1);
    asyncTask1.execute();
    asyncTask2 = new MyAsyncTask(progressBar2);
    asyncTask2.execute();
    asyncTask3 = new MyAsyncTask(progressBar3);
    asyncTask3.execute();
    asyncTask4 = new MyAsyncTask(progressBar4);
    StartAsyncTaskInParallel(asyncTask4);
    asyncTask5 = new MyAsyncTask(progressBar5);
    StartAsyncTaskInParallel(asyncTask5);
   }});

 }
 
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 private void StartAsyncTaskInParallel(MyAsyncTask task) {
     if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     else
         task.execute();
 }

}

activity_main.xml
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.androidparallelasynctask.MainActivity" >
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <Button
        android:id="@+id/start"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Start"/>
    
    <ProgressBar
        android:id="@+id/progressbar1"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0" />
    <ProgressBar
        android:id="@+id/progressbar2"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0" />
    <ProgressBar
        android:id="@+id/progressbar3"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0" />
    <ProgressBar
        android:id="@+id/progressbar4"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0" />
    <ProgressBar
        android:id="@+id/progressbar5"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0" />
        
</LinearLayout>

This video show how it run on devices running various Android version, include:
- Nexus 7 (1st generation), Android 4.4.2
- HTC One X, Android 4.2.2
- HTC Flyer, Android 3.2.1
- Nexus One, Android 2.3.6


download filesDownload the files.

Friday, April 25, 2014

Implement PagerTitleStrip on ViewPager

PagerTitleStrip is a non-interactive indicator of the current, next, and previous pages of a ViewPager. It is intended to be used as a child view of a ViewPager widget.

This example modify from last example.


Modify activity_main.xml, add PagerTitleStrip as a child view of a ViewPager.
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.androidviewpagerapp.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <android.support.v4.view.ViewPager
        android:id="@+id/myviewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <android.support.v4.view.PagerTitleStrip
            android:id="@+id/titlestrip"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        
    </android.support.v4.view.ViewPager>
        
</LinearLayout>

Modify MainActivity.java, override getPageTitle() method in MyPagerAdapter to return CharSequence of PageTitle.
package com.example.androidviewpagerapp;

import android.support.v4.view.PagerAdapter;
import android.support.v4.view.PagerTitleStrip;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;

public class MainActivity extends Activity {

 ViewPager viewPager;
 MyPagerAdapter myPagerAdapter;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  viewPager = (ViewPager)findViewById(R.id.myviewpager);
  myPagerAdapter = new MyPagerAdapter();
  viewPager.setAdapter(myPagerAdapter);
  
  PagerTitleStrip pagerTitleStrip = (PagerTitleStrip)findViewById(R.id.titlestrip);

 }
 
 private class MyPagerAdapter extends PagerAdapter{
  
  int NumberOfPages = 5;
  
  int[] res = { 
   android.R.drawable.ic_dialog_alert,
   android.R.drawable.ic_menu_camera,
   android.R.drawable.ic_menu_compass,
   android.R.drawable.ic_menu_directions,
   android.R.drawable.ic_menu_gallery};
  int[] backgroundcolor = { 
   0xFF101010,
   0xFF202020,
   0xFF303030,
   0xFF404040,
   0xFF505050};
  
  String[] title = {
   "Page 1",
   "pAge 2",
   "paGe 3",
   "pagE 4",
   "page 5"};

  @Override
  public int getCount() {
   return NumberOfPages;
  }

  @Override
  public boolean isViewFromObject(View view, Object object) {
   return view == object;
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
   
      
      TextView textView = new TextView(MainActivity.this);
      textView.setTextColor(Color.WHITE);
      textView.setTextSize(30);
      textView.setTypeface(Typeface.DEFAULT_BOLD);
      textView.setText(String.valueOf(position));
      
      ImageView imageView = new ImageView(MainActivity.this);
      imageView.setImageResource(res[position]);
      LayoutParams imageParams = new LayoutParams(
        LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
      imageView.setLayoutParams(imageParams);
      
      LinearLayout layout = new LinearLayout(MainActivity.this);
      layout.setOrientation(LinearLayout.VERTICAL);
      LayoutParams layoutParams = new LayoutParams(
        LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
      layout.setBackgroundColor(backgroundcolor[position]);
      layout.setLayoutParams(layoutParams);
      layout.addView(textView);
      layout.addView(imageView);
      
      final int page = position;
      layout.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     Toast.makeText(MainActivity.this, 
      "Page " + page + " clicked", 
      Toast.LENGTH_LONG).show();
    }});
      
      container.addView(layout);
      return layout;
  }

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
   container.removeView((LinearLayout)object);
  }

  @Override
  public CharSequence getPageTitle(int position) {
   return title[position];
  }

 }

}




download filesDownload the files.

- How to Set text size and color of PagerTitleStrip

Thursday, April 24, 2014

Example of ViewPager with custom PagerAdapter

This example show how to implement ViewPager with custom PagerAdapter. (Another example(s) implement ViewPager with FragmentPagerAdapter, read HERE)


Modify activity_main.xml to include android.support.v4.view.ViewPager in layout.
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.androidviewpagerapp.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <android.support.v4.view.ViewPager
        android:id="@+id/myviewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
        
</LinearLayout>

MainActivity.java
package com.example.androidviewpagerapp;

import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;

public class MainActivity extends Activity {

 ViewPager viewPager;
 MyPagerAdapter myPagerAdapter;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  viewPager = (ViewPager)findViewById(R.id.myviewpager);
  myPagerAdapter = new MyPagerAdapter();
  viewPager.setAdapter(myPagerAdapter);

 }
 
 private class MyPagerAdapter extends PagerAdapter{
  
  int NumberOfPages = 5;
  
  int[] res = { 
   android.R.drawable.ic_dialog_alert,
   android.R.drawable.ic_menu_camera,
   android.R.drawable.ic_menu_compass,
   android.R.drawable.ic_menu_directions,
   android.R.drawable.ic_menu_gallery};
  int[] backgroundcolor = { 
   0xFF101010,
   0xFF202020,
   0xFF303030,
   0xFF404040,
   0xFF505050};

  @Override
  public int getCount() {
   return NumberOfPages;
  }

  @Override
  public boolean isViewFromObject(View view, Object object) {
   return view == object;
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
   
      
      TextView textView = new TextView(MainActivity.this);
      textView.setTextColor(Color.WHITE);
      textView.setTextSize(30);
      textView.setTypeface(Typeface.DEFAULT_BOLD);
      textView.setText(String.valueOf(position));
      
      ImageView imageView = new ImageView(MainActivity.this);
      imageView.setImageResource(res[position]);
      LayoutParams imageParams = new LayoutParams(
        LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
      imageView.setLayoutParams(imageParams);
      
      LinearLayout layout = new LinearLayout(MainActivity.this);
      layout.setOrientation(LinearLayout.VERTICAL);
      LayoutParams layoutParams = new LayoutParams(
        LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
      layout.setBackgroundColor(backgroundcolor[position]);
      layout.setLayoutParams(layoutParams);
      layout.addView(textView);
      layout.addView(imageView);
      
      final int page = position;
      layout.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     Toast.makeText(MainActivity.this, 
      "Page " + page + " clicked", 
      Toast.LENGTH_LONG).show();
    }});
      
      container.addView(layout);
      return layout;
  }

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
   container.removeView((LinearLayout)object);
  }

 }

}


download filesDownload the files.

Next:
Implement PagerTitleStrip on ViewPager
Implement OnPageChangeListener for ViewPager to monitor the current visible page

Wednesday, April 23, 2014

Java Programming for Android Developers For Dummies

Get started creating Android apps with Java in no time!

Java Programming for Android Developers For Dummies

The demand for Android apps is not slowing down but many mobile developers who want to create Android apps lack the necessary Java background. This beginner guide gets you up and running with using Java to create Android apps with no prior knowledge or experienced necessary!
  • Shows you the basic Java development concepts and techniques that are necessary to develop Android apps
  • Explores what goes into creating an Android app to give you a better understanding of the various elements
  • Addresses how to deal with standard programming challenges and debugging
Beginning Android Programming with Java For Dummies puts you well on your way toward creating Android apps quickly with Java.

Android Hacker's Handbook

The first comprehensive guide to discovering and preventing attacks on the Android OS

Android Hacker's Handbook

As the Android operating system continues to increase its share of the smartphone market, smartphone hacking remains a growing threat. Written by experts who rank among the world's foremost Android security researchers, this book presents vulnerability discovery, analysis, and exploitation tools for the good guys. Following a detailed explanation of how the Android OS works and its overall security architecture, the authors examine how vulnerabilities can be discovered and exploits developed for various system components, preparing you to defend against them.

If you are a mobile device administrator, security researcher, Android app developer, or consultant responsible for evaluating Android security, you will find this guide is essential to your toolbox.
  • A crack team of leading Android security researchers explain Android security risks, security design and architecture, rooting, fuzz testing, and vulnerability analysis
  • Covers Android application building blocks and security as well as debugging and auditing Android apps
  • Prepares mobile device administrators, security researchers, Android app developers, and security consultants to defend Android systems against attack
Android Hacker's Handbook is the first comprehensive resource for IT professionals charged with smartphone security.

Sunday, April 20, 2014

Example of programming Android NFC

This example send Uri between Android devices using NFC.


To using NFC on your Android app, modify AndroidManifest.xml.
  • This example target minSdkVersion="16"
  • Add permission of "android.permission.NFC"
  • Add intent-filter of "android.nfc.action.NDEF_DISCOVERED"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidnfc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.NFC"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidnfc.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="http"
                    android:host="android-er.blogspot.com"
                    android:pathPrefix="/" />
            </intent-filter>
            
        </activity>
    </application>

</manifest>


MainActivity.java
package com.example.androidnfc;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcAdapter.OnNdefPushCompleteCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements 
 CreateNdefMessageCallback, OnNdefPushCompleteCallback{
 
 TextView textInfo;
 
 NfcAdapter nfcAdapter;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textInfo = (TextView)findViewById(R.id.info);

  nfcAdapter = NfcAdapter.getDefaultAdapter(this);
  if(nfcAdapter==null){
   Toast.makeText(MainActivity.this, 
    "nfcAdapter==null, no NFC adapter exists", 
    Toast.LENGTH_LONG).show();
  }else{
   Toast.makeText(MainActivity.this, 
     "Set Callback(s)", 
     Toast.LENGTH_LONG).show();
   nfcAdapter.setNdefPushMessageCallback(this, this);
   nfcAdapter.setOnNdefPushCompleteCallback(this, this);
  }
 }

 @Override
 protected void onResume() {
  super.onResume();
  Intent intent = getIntent();
  String action = intent.getAction();
  if(action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)){
   Parcelable[] parcelables = 
    intent.getParcelableArrayExtra(
      NfcAdapter.EXTRA_NDEF_MESSAGES);
   NdefMessage inNdefMessage = (NdefMessage)parcelables[0];
   NdefRecord[] inNdefRecords = inNdefMessage.getRecords();
   NdefRecord NdefRecord_0 = inNdefRecords[0];
   String inMsg = new String(NdefRecord_0.getPayload());
   textInfo.setText(inMsg);
  }
 }

 @Override
 protected void onNewIntent(Intent intent) {
  setIntent(intent);
 }

 @Override
 public void onNdefPushComplete(NfcEvent event) {
  
  final String eventString = "onNdefPushComplete\n" + event.toString();
  runOnUiThread(new Runnable() {
   
   @Override
   public void run() {
    Toast.makeText(getApplicationContext(), 
      eventString, 
      Toast.LENGTH_LONG).show();
   }
  });

 }

 @Override
 public NdefMessage createNdefMessage(NfcEvent event) {
  NdefRecord rtdUriRecord = NdefRecord.createUri("http://android-er.blogspot.com/");

  NdefMessage ndefMessageout = new NdefMessage(rtdUriRecord);
  return ndefMessageout;
 }

}


activity_main.xml
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.androidnfc.MainActivity" >
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>


download filesDownload the files.

Related:
Communication between Android using NFC to send text

Saturday, April 19, 2014

Strategies for Developing Cross-Device Applications with Visual Studio 2013

This session will cover the strategic decisions developers have to make when targeting multiples devices in application. The video will explore the tools and technologies that available in Visual Studio 2013 for both web and native applications that target Windows, iOS and Android devices, as well as best practices to reuse code and skills across them.

source: http://channel9.msdn.com/Events/Build/2014/2-586

Friday, April 18, 2014

What's Google got to do with games?

Video from Google Developers Summit keynote, connecting you with the gaming community. How to use Google's latest technologies to build, distribute, promote, and monetize your games.


Thursday, April 17, 2014

What's New in Ubuntu 14.04 LTS

Released on April 17, 2014, Ubuntu 14.04 is the latest Long Term Support release of the world's most popular open-source operating system.

See What's New in Ubuntu 14.04 LTS

Remote control Windows from Ubuntu Linux with Chrome Remote Desktop

With Chrome Remote Desktop "Enable remote connection" on Windows PC (refer to the last post), you can remote control it from Linux PC with Chrome Remote Desktop.

Wednesday, April 16, 2014

Remote access shared computer using Chrome Remote Desktop app on Android

[Updated@2015-09-09: check the updated post "Remote control Windows 10 from Android using Chrome Remote Desktop"]

The video show how to setup Chrome Remote Desktop on shared computer (running Windows 8.1) to enable remote access with PIN. Then you can control your computer remotely using Chrome Remote Desktop App on Android device.


In shared computer, Windows 8.1 in this example:
  • You need to log-in with a Google account.
  • Install Chrome Remote Desktop in Google Chrome.
  • Run the Chrome Remote Desktop App in Chrome's App pane.
  • Click the Get Started button in "My Computer" box.
  • Click "Enable remote connection".
  • Enter and re-enter the PIN you want, then OK.
  • You will be ask to grant permission by Windows.
  • Your computer, win-8b in this example, will be shown in "My Computer" box.
In Android:
  • Simple install Chrome Remote Desktop App.
  • Run it and select the same Google account in computer side.
  • You can see the computer, win-8b, in the list.

The following video show remote control shared PC from Android Phone. In this example, the Android phone connect Internet using 3G network. The shared PC running Windows 8.1, with Chrome Remote Desktop installed and setup "Enable remote connection", power-on with internet connection (via home network), even have not log-in Windows session, no any port forwarding setting on router. 



You can also access your shared Windows from Ubuntu Linux, read next post "Remote control Windows from Ubuntu Linux with Chrome Remote Desktop".

Related: Share computer desktop with Chrome Remote Desktop


Share computer desktop with Chrome Remote Desktop

Chrome Remote Desktop allows users to remotely access another computer through Chrome browser or a Chromebook.  Computers can be made available on an short-term basis for scenarios such as ad hoc remote support, or on a more long-term basis for remote access to your applications and files.  All connections are fully secured.

Chrome Remote Desktop is fully cross-platform.  Provide remote assistance to Windows, Mac and Linux users, or access your Windows (XP and above) and Mac (OS X 10.6 and above) desktops at any time, all from the Chrome browser on virtually any device, including Chromebooks.

This video show installation and setup Chrome Remote Desktop on Windows 8.1 to be shared. And install and setup on Ubuntu to access the shared Windows desktop.



Related: Access shared computer using Chrome Remote Desktop app on Android

Tuesday, April 15, 2014

Display currency symbols

This example TRY to show various available currency symbols on Android. This symbols reference to the file http://www.unicode.org/charts/PDF/U20A0.pdf, it contains an excerpt from the character code tables and list of character names for The Unicode Standard, Version 6.3.

Please note that some symbols cannot be shown, because it have not been installed in Android system.


MainActivity.java
package com.example.androidshowcurrency;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.AdapterView.OnItemSelectedListener;

public class MainActivity extends Activity {
 
 MyCurrency[] MyCurrencyAll ={
   new MyCurrency("$", "dollar sign"),
   new MyCurrency("¢", "cent sign"),
   new MyCurrency("£", "pound sign"),
   new MyCurrency("¤", "currency sign"),
   new MyCurrency("¥", "yen sign"),
   new MyCurrency("ƒ", "latin small letter f with hook"),
   new MyCurrency("", "afghani sign"),
   new MyCurrency("৲", "bengali rupee mark"),
   new MyCurrency("૱", "gujarati rupee sign"),
   new MyCurrency("௹", "tamil rupee sign"),
   new MyCurrency("฿", "thai currency symbol baht"),
   new MyCurrency("¤", "khmer currency symbol riel"),
   new MyCurrency("ℳ", "script capital m"),
   new MyCurrency("元", "cjk unified ideograph-5143"),
   new MyCurrency("円", "cjk unified ideograph-5186"),
   new MyCurrency("圆", "cjk unified ideograph-5706"),
   new MyCurrency("圓", "cjk unified ideograph-5713"),
   new MyCurrency("", "rial sign"),
   new MyCurrency("₠", "EURO-CURRENCY SIGN"),
   new MyCurrency("₡", "COLON SIGN"),
   new MyCurrency("₢", "CRUZEIRO SIGN"),
   new MyCurrency("₣", "FRENCH FRANC SIGN"),
   new MyCurrency("₤", "LIRA SIGN"),
   new MyCurrency("₥", "MILL SIGN"),
   new MyCurrency("₦", "NAIRA SIGN"),
   new MyCurrency("₧", "PESETA SIGN"),
   new MyCurrency("₨", "RUPEE SIGN"),
   new MyCurrency("₩", "WON SIGN"),
   new MyCurrency("₪", "NEW SHEQEL SIGN"),
   new MyCurrency("₫", "DONG SIGN"),
   new MyCurrency("€", "EURO SIGN"),
   new MyCurrency("₭", "KIP SIGN"),
   new MyCurrency("₮", "TUGRIK SIGN"),
   new MyCurrency("₯", "DRACHMA SIGN"),
   new MyCurrency("₰", "GERMAN PENNY SIGN"),
   new MyCurrency("₱", "PESO SIGN"),
   new MyCurrency("₲", "GUARANI SIGN"),
   new MyCurrency("₳", "AUSTRAL SIGN"),
   new MyCurrency("₴", "HRYVNIA SIGN"),
   new MyCurrency("₵", "CEDI SIGN"),
   new MyCurrency("₶", "LIVRE TOURNOIS SIGN"),
   new MyCurrency("₷", "SPESMILO SIGN"),
   new MyCurrency("₸", "TENGE SIGN"),
   new MyCurrency("₹", "INDIAN RUPEE SIGN"),
   new MyCurrency("₺", "TURKISH LIRA SIGN")
  };
 
 Spinner spinnerCurrency;
 TextView textBigCurrency;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  textBigCurrency = (TextView)findViewById(R.id.bigcurrency);
  spinnerCurrency = (Spinner)findViewById(R.id.spinnerCurrency);
  
  MySpinnerAdapter adapterCurr = 
    new MySpinnerAdapter(MainActivity.this, 
      R.layout.row, 
      MyCurrencyAll);
  spinnerCurrency.setAdapter(adapterCurr);
  spinnerCurrency.setOnItemSelectedListener(onItemSelectedListener);

 }
 
 OnItemSelectedListener onItemSelectedListener =
  new OnItemSelectedListener(){

  @Override
  public void onItemSelected(AdapterView<?> parent, 
    View view, int position, long id) {
   MyCurrency curr = (MyCurrency)(parent.getItemAtPosition(position));
   textBigCurrency.setText(String.valueOf(curr.getSymbol())); 
  }

  @Override
  public void onNothingSelected(AdapterView<?> parent) {}

 };

 // define our custom class
 public class MyCurrency {

  private String symbol;
  private String desc;

  public MyCurrency(String symbol, String desc) {
   this.symbol = symbol;
   this.desc = desc;
  }


  public String getSymbol() {
   return this.symbol;
  }

  public String getDesc() {
   return this.desc;
  }
 }

 // custom adapter
 public class MySpinnerAdapter extends ArrayAdapter<MyCurrency> {

  private MyCurrency[] myCurrencyArray;

  public MySpinnerAdapter(Context context, int textViewResourceId,
    MyCurrency[] myObjs) {
   super(context, textViewResourceId, myObjs);
   this.myCurrencyArray = myObjs;
  }

  public int getCount() {
   return myCurrencyArray.length;
  }

  public MyCurrency getItem(int position) {
   return myCurrencyArray[position];
  }

  public long getItemId(int position) {
   return position;
  }

  @Override
  public View getView(final int position, View convertView,
    ViewGroup parent) {
   return getCustomView(position, convertView, parent);
  }

  @Override
  public View getDropDownView(int position, View convertView,
    ViewGroup parent) {
   return getCustomView(position, convertView, parent);
  }

  private View getCustomView(int position, View convertView,
    ViewGroup parent) {
   LayoutInflater inflater = getLayoutInflater();
   View view = inflater.inflate(R.layout.row, parent, false);

   TextView textSymbol = (TextView) view
     .findViewById(R.id.textSymbol);
   textSymbol.setText(myCurrencyArray[position].getSymbol());
   TextView textDesc = (TextView) view
     .findViewById(R.id.textDesc);
   textDesc.setText(myCurrencyArray[position].getDesc());

   return view;
  }
 }

}

/res/layout/row.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp" >
    
    <TextView 
        android:id="@+id/textSymbol"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold" />
    <TextView 
        android:id="@+id/textDesc"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textStyle="italic" />
</LinearLayout>

/res/layout/activity_main.xml
<LinearLayout 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: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"
    tools:context="com.example.androidspinnertext.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <Spinner
        android:id="@+id/spinnerCurrency"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/bigcurrency"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_horizontal"
        android:textSize="150sp" />
    
</LinearLayout>


download filesDownload the files.

Or, download the APK HERE.

Related:
Display available currencies java.util.Currency

Pro Android UI

Pro Android UI

If you’re an Android application developer, chances are you’re using fixed, scrolling, swipe-able, and other cutting-edge custom UI Designs in your Android development projects. These UI Design approaches as well as other Android ViewGroup UI layout containers are the bread and butter of Pro Android User Interface (UI) design and Android User Experience (UX) design and development.

Using a top down approach, Pro Android UI shows you how to design and develop the best user interface for your app, while taking into account the varying device form factors in the increasingly fragmented Android environment. Pro Android UI aims to be the ultimate reference and customization cookbook for your Android UI Design, and as such will be useful to experienced developers as well as beginners.

With Android’s powerful UI layout classes, you can easily create everything from the simplest of lists to fully tricked-out user interfaces. While using these UI classes for boring, standard user interfaces can be quite simple, customizing a unique UI design can often become extremely challenging.


What you’ll learn

  • How to design and develop a sleek looking and highly functional user interface (UI) design and experience (UX) design using Android APIs
  • What Android layout containers are, and how to best leverage them
  • How to design user-friendly UI layouts that conform to Android UI guidelines
  • What, when, why and how to use fundamental Android UI layout containers (ViewGroup subclasses) and Android UI widgets (View subclasses)
  • How to use new media assets such as images, video, and animation in a UI
  • How to create UI Fragments for UI design for specific ActionBar or Activity classes that you wish to create for UI designs within your applications
  • Scaling UI Design for the various Android smartphone and tablet form factors

  • Who this book is for
    This book is for experienced Android app developers.  It can also be for app developers and UI designers working on other platforms like iOS and BlackBerry who might also be interested in Android.

    Table of Contents

    Part I. Introduction to the Core Classes for Android UI Design: Development Tools, Layout Containers and Widgets
    1. Android UI Design Tools: Setting Up Your Android Development System
    2. Android UI Layouts: Layout Containers and the ViewGroup Class
    3. Android UI Widgets: User Interface Widgets and the View Class

    Part II. Introduction to Android Menu Class for UI Design: OptionsMenu, ContextMenu, PopupMenu and ActionBar
    4. Android UI Options Menus: OptionsMenu Class and an Introduction to the Android ActionBar
    5. Android UI Local Menus: The ContextMenu Class and PopupMenu Class
    6. Android UI Action Bar: Advanced ActionBar Design & ActionBar Class

    Part III. Android UI: Layout Considerations, Concepts & UI Containers: LinearLayout, RelativeLayout, FrameLayout
    7. Android UI Design Considerations: Styles, Screen Density Targets and New Media Formats
    8. Android UI Design Concepts: Wire-framing & UI Layout Design Patterns
    9. Android UI Layout Conventions, Differences and Approaches
    10. Android UI Theme Design & Digital Media Concepts

    Part IV. Basic Android UI Design: Basic Layout Containers: FrameLayout, LinearLayout,
    RelativeLayout, GridLayout
    11. Android’s FrameLayout Class: Using Digital Video in your UI Design
    12. Android’s LinearLayout Class: Horizontal and Vertical UI Design
    13. Android’s RelativeLayout Class: Complex UI Design Via a Single Layout Container
    14. Android’s GridLayout Class: Optimized UI Design using a Grid-based Layout

    Part V. Advanced Android UI Design: Advanced Layout Containers: DrawerLayout, SlidingPane, ViewPager, Strips
    15. Android DrawerLayout Class: Using Left and Right Side UI Drawer Design
    16. Android SlidingPaneLayout Class: Optimized UI Design using a Grid-based Layout Container
    17. Android ViewPager Class: Using View Paging to Navigate Complex View Hierarchies
    18. Android PagerTabStrip and PagerTitleStrip Classes: Design Navigation UI Elements for the ViewPager Layout

    Sunday, April 13, 2014

    Custom Spinner with different normal view and drop-down view

    It's another example of custom Spinner, which have different normal view and drop-down view, using custom object and adapter for the spinner. Our custom object have two String, one for display text, another for Internet address.

    In normal view, returned by getView() of our custom ArrayAdapter, there are one TextView to show the display text, and another button to open the target address if clicked. In drop-down view, returned by getDropDownView() of our custom ArrayAdapter, there are two TextView to show the display text, and the target address.


    /res/layout/row.xml, the layout of the normal view.
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
        
        <TextView 
            android:id="@+id/gotext"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:textStyle="bold"/>
        <Button
            android:id="@+id/gobutton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    

    /res/layout/dropdown.xml, the layout of the drop-down view.
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="10dp" >
        
        <TextView 
            android:id="@+id/gotext"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:textStyle="bold" />
        <TextView 
            android:id="@+id/goaddr"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textStyle="italic" />
    </LinearLayout>
    

    /res/layout/activity_main.xml, the layout of our activity.
    <LinearLayout 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: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"
        tools:context="com.example.androidspinnertext.MainActivity" >
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:autoLink="web"
            android:text="http://android-er.blogspot.com/"
            android:textStyle="bold" />
        
        <Spinner
            android:id="@+id/spinnergo"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/textgo"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        
    </LinearLayout>
    

    MainActivity.java
    package com.example.androidspinnertext;
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemSelectedListener;
    import android.widget.ArrayAdapter;
    import android.widget.Button;
    import android.widget.Spinner;
    import android.widget.TextView;
    
    public class MainActivity extends Activity {
    
     MyClass[] objGo ={
      new MyClass("Android-er", "http://android-er.blogspot.com/"),
      new MyClass("Arduino-er", "http://arduino-er.blogspot.com/"),
      new MyClass("Hello Raspberry Pi", "http://helloraspberrypi.blogspot.com/"),
      new MyClass("MyPhotoBlog", "http://photo-er.blogspot.com/"),
      new MyClass("g+ Androider+", "https://plus.google.com/102969667192015169220"),
      new MyClass("Youtube playlist: Android Development", "http://www.youtube.com/playlist?list=PLP7qPet500deChwUlhq-GsDl8Tun4_WMD"),
      new MyClass("Google Play", "https://play.google.com/store")
     };
     
     Spinner spinnerGo;
     TextView textViewGo;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      
      textViewGo = (TextView)findViewById(R.id.textgo);
      spinnerGo = (Spinner)findViewById(R.id.spinnergo);
      MySpinnerAdapter adapterGo = 
       new MySpinnerAdapter(MainActivity.this, 
         R.layout.row, 
         objGo);
      spinnerGo.setAdapter(adapterGo);
      //spinnerGo.setOnItemSelectedListener(onItemSelectedListenerGo);
    
     }
     
     OnItemSelectedListener onItemSelectedListenerGo =
      new OnItemSelectedListener(){
    
       @Override
       public void onItemSelected(AdapterView<?> parent, View view,
         int position, long id) {
        MyClass obj = (MyClass)(parent.getItemAtPosition(position));
        textViewGo.setText(String.valueOf(obj.getTarget()));
       }
    
       @Override
       public void onNothingSelected(AdapterView<?> parent) {}
    
     };
     
     //define our custom class
     public class MyClass{
    
      private String text;
         private String target;
    
         public MyClass(String text, String target){
          this.text = text;
          this.target = target;
         }
         
         public void setText(String text){
             this.text = text;
         }
    
         public String getText(){
             return this.text;
         }
    
         public void setValue(String target){
             this.target = target;
         }
    
         public String getTarget(){
             return this.target;
         }
     }
     
     //custom adapter
     public class MySpinnerAdapter extends ArrayAdapter<MyClass>{
    
         private MyClass[] myObjs;
    
         public MySpinnerAdapter(Context context, int textViewResourceId,
           MyClass[] myObjs) {
             super(context, textViewResourceId, myObjs);
             this.myObjs = myObjs;
         }
    
         public int getCount(){
            return myObjs.length;
         }
    
         public MyClass getItem(int position){
            return myObjs[position];
         }
    
         public long getItemId(int position){
            return position;
         }
    
         @Override
         public View getView(final int position, View convertView, ViewGroup parent) {
          LayoutInflater inflater = getLayoutInflater(); 
          View spView = inflater.inflate(R.layout.row, parent, false);
          
          TextView sp_GoText = (TextView)spView.findViewById(R.id.gotext);
          sp_GoText.setText(myObjs[position].getText());
          
          Button sp_GoButton = (Button)spView.findViewById(R.id.gobutton);
          sp_GoButton.setText(myObjs[position].getTarget());
          
          sp_GoButton.setOnClickListener(new OnClickListener(){
    
        @Override
        public void onClick(View v) {
         Uri uri = Uri.parse(myObjs[position].getTarget());
         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
         startActivity(intent);
        }});
          
          return spView;
         }
    
         @Override
         public View getDropDownView(int position, View convertView,
                 ViewGroup parent) {
          LayoutInflater inflater = getLayoutInflater(); 
          View dropDownView = inflater.inflate(R.layout.dropdown, parent, false);
          
          TextView dd_GoText = (TextView)dropDownView.findViewById(R.id.gotext);
          dd_GoText.setText(myObjs[position].getText());
          
          TextView dd_GoAddr = (TextView)dropDownView.findViewById(R.id.goaddr);
          dd_GoAddr.setText(myObjs[position].getTarget());
          
          return dropDownView;
              
         }
         
     }
    
    }
    



    download filesDownload the files.

    Friday, April 11, 2014

    Free ebook: Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition

    Visit HERE to download the Free ebook: Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition

    Spinner with different display text and return value

    In the most basic Spinner implementation, selected item can be retrieved by calling parent.getItemAtPosition(position) in onItemSelected() method in OnItemSelectedListener. It will be the same object of the display items, as show in the spinner0 of the example.

    Sometimes, we want to display some meaningful text in Spinner (such as "Sunday", "Monday"...), but return some other value when any item selected (such as 0, 2...).

    Spinner with different display text and return value
    Here I show two approaches:
    • The first one may be the simplest method, spinner1 in the example. Create another array to hold the values we want to return. And return the coresponding item on position in onItemSelected().
    • The second approach implement our custom class to hold the display text and the return value. And implement our custom Adapter, as shown in spinner2 in the example.

    package com.example.androidspinnertext;
    
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemSelectedListener;
    import android.widget.ArrayAdapter;
    import android.widget.Spinner;
    import android.widget.TextView;
    
    public class MainActivity extends Activity {
     
     String[] text0 = { "Sunday", "Monday", "Tuesday", 
       "Wednesday", "Thursday", "Friday", "Saturday" };
     
     String[] text1 = { "SUNDAY", "MONDAY", "TUESDAY", 
       "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY" };
     int[] val1 = { 0, 1, 2, 3, 4, 5, 6};
     
     MyClass[] obj2 ={
      new MyClass("SUN", 0),
      new MyClass("MON", 1),
      new MyClass("TUE", 2),
      new MyClass("WED", 3),
      new MyClass("THU", 4),
      new MyClass("FRI", 5),
      new MyClass("SAT", 6)
     };
     
     Spinner spinner0, spinner1, spinner2;
     TextView textView0, textView1, textView2;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      
      textView0 = (TextView)findViewById(R.id.text0);
      spinner0 = (Spinner)findViewById(R.id.spinner0);
      ArrayAdapter<String> adapter0 = 
       new ArrayAdapter<String>(MainActivity.this, 
         android.R.layout.simple_spinner_item, text0);
      adapter0.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
      spinner0.setAdapter(adapter0);
      spinner0.setOnItemSelectedListener(onItemSelectedListener0);
      
      textView1 = (TextView)findViewById(R.id.text1);
      spinner1 = (Spinner)findViewById(R.id.spinner1);
      ArrayAdapter<String> adapter1 = 
       new ArrayAdapter<String>(MainActivity.this, 
         android.R.layout.simple_spinner_item, text1);
      adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
      spinner1.setAdapter(adapter1);
      spinner1.setOnItemSelectedListener(onItemSelectedListener1);
      
      textView2 = (TextView)findViewById(R.id.text2);
      spinner2 = (Spinner)findViewById(R.id.spinner2);
      MySpinnerAdapter adapter2 = 
       new MySpinnerAdapter(MainActivity.this, 
         android.R.layout.simple_spinner_item, obj2);
      //adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
      spinner2.setAdapter(adapter2);
      spinner2.setOnItemSelectedListener(onItemSelectedListener2);
    
     }
     
     OnItemSelectedListener onItemSelectedListener0 =
      new OnItemSelectedListener(){
    
       @Override
       public void onItemSelected(AdapterView<?> parent, View view,
         int position, long id) {
        String s0 = (String)parent.getItemAtPosition(position);
        textView0.setText(s0);
       }
    
       @Override
       public void onNothingSelected(AdapterView<?> parent) {}
     };
     
     OnItemSelectedListener onItemSelectedListener1 =
      new OnItemSelectedListener(){
    
       @Override
       public void onItemSelected(AdapterView<?> parent, View view,
         int position, long id) {
        String s1 = String.valueOf(val1[position]);
        textView1.setText(s1);
       }
    
       @Override
       public void onNothingSelected(AdapterView<?> parent) {}
    
     };
     
     OnItemSelectedListener onItemSelectedListener2 =
      new OnItemSelectedListener(){
    
       @Override
       public void onItemSelected(AdapterView<?> parent, View view,
         int position, long id) {
        MyClass obj = (MyClass)(parent.getItemAtPosition(position));
        textView2.setText(String.valueOf(obj.getValue()));
       }
    
       @Override
       public void onNothingSelected(AdapterView<?> parent) {}
    
     };
     
     //define our custom class
     public class MyClass{
    
      private String text;
         private int value;
         
    
         public MyClass(String text, int value){
          this.text = text;
          this.value = value;
         }
         
         public void setText(String text){
             this.text = text;
         }
    
         public String getText(){
             return this.text;
         }
    
         public void setValue(int value){
             this.value = value;
         }
    
         public int getValue(){
             return this.value;
         }
     }
     
     //custom adapter
     public class MySpinnerAdapter extends ArrayAdapter<MyClass>{
    
         private Context context;
         private MyClass[] myObjs;
    
         public MySpinnerAdapter(Context context, int textViewResourceId,
           MyClass[] myObjs) {
             super(context, textViewResourceId, myObjs);
             this.context = context;
             this.myObjs = myObjs;
         }
    
         public int getCount(){
            return myObjs.length;
         }
    
         public MyClass getItem(int position){
            return myObjs[position];
         }
    
         public long getItemId(int position){
            return position;
         }
    
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             TextView label = new TextView(context);
             label.setText(myObjs[position].getText());
             return label;
         }
    
         @Override
         public View getDropDownView(int position, View convertView,
                 ViewGroup parent) {
             TextView label = new TextView(context);
             label.setText(myObjs[position].getText());
             return label;
         }
     }
    
    }
    

    <LinearLayout 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: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"
        tools:context="com.example.androidspinnertext.MainActivity" >
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:autoLink="web"
            android:text="http://android-er.blogspot.com/"
            android:textStyle="bold" />
    
        <Spinner
            android:id="@+id/spinner0"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/text0"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
    
        <Spinner
            android:id="@+id/spinner1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/text1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        
        <Spinner
            android:id="@+id/spinner2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/text2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        
    </LinearLayout>
    



    download filesDownload the files.

    Thursday, April 10, 2014

    Get user-agent of your WebView/browser

    To get the user-agent of your browser in HTML code, you can read navigator.userAgent.

    This example show user-agent of your WebView, and also include a link to HTML5TEST to test your HTML compatibility. It's how it run on Nexus 7 running Android 4.4.2 KitKat, and on HTC One X running 4.2.2.

    Run on Nexus 7, Android 4.4.2


    Run on HTC One X, Android 4.2.2


    Android 4.4 (KitKat) includes a new WebView component based on the Chromium open source project. The new WebView includes an updated version of the V8 JavaScript engine and support for modern web standards that were missing in the old WebView. It also shares the same rendering engine as Chrome for Android, so rendering should be much more consistent between the WebView and Chrome.

    The new WebView adds Chrome/_version_ to the user-agent string. 

    ~ about WebView for Android

    /assets/mypage.html
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width; user-scalable=0;" />
    <title>My HTML</title>
    </head>
    <body>
    <h1>MyHTML</h1>
    <p id="mytext">Hello!</p>
    <p>my User-agent</P>
    <p id="myUA">Hello!</p>
    <br/>
    visit: <a href="http://html5test.com/">http://html5test.com/</a>
    <script language="javascript">
    document.getElementById("myUA").innerHTML=navigator.userAgent;
    </script>
    
    </body>
    </html>
    

    MainActivity.java
    package com.example.androidbrowser;
    
    import android.support.v7.app.ActionBarActivity;
    import android.support.v4.app.Fragment;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.ViewGroup;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    
    public class MainActivity extends ActionBarActivity {
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
    
      if (savedInstanceState == null) {
       getSupportFragmentManager().beginTransaction()
         .add(R.id.container, new PlaceholderFragment()).commit();
      }
     }
    
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
    
      // Inflate the menu; this adds items to the action bar if it is present.
      getMenuInflater().inflate(R.menu.main, menu);
      return true;
     }
    
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
      // Handle action bar item clicks here. The action bar will
      // automatically handle clicks on the Home/Up button, so long
      // as you specify a parent activity in AndroidManifest.xml.
      int id = item.getItemId();
      if (id == R.id.action_settings) {
       return true;
      }
      return super.onOptionsItemSelected(item);
     }
    
     /**
      * A placeholder fragment containing a simple view.
      */
     public static class PlaceholderFragment extends Fragment {
    
      WebView myBrowser;
    
      public PlaceholderFragment() {
      }
    
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
       View rootView = inflater.inflate(R.layout.fragment_main, container,
         false);
       myBrowser = (WebView) rootView.findViewById(R.id.mybrowser);
    
       myBrowser.getSettings().setJavaScriptEnabled(true);
       myBrowser.loadUrl("file:///android_asset/mypage.html");
       
       //open new web page in the same myBrowser webview
       myBrowser.setWebViewClient(new WebViewClient() {
    
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
         view.loadUrl(url);
                  return false;
        }
    
          });
    
    
       return rootView;
      }
    
     }
    
    }
    

    /res/layout/fragment_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:autoLink="web"
            android:text="http://android-er.blogspot.com/"
            android:textStyle="bold" />
    
        <WebView
            android:id="@+id/mybrowser"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    
    </LinearLayout>
    

    /res/layout/activity_main.xml, default generated without changed.
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.androidbrowser.MainActivity"
        tools:ignore="MergeRootFrame" />
    

    In order to visit Internet, permission of "android.permission.INTERNET" is needed in AndroidManifest.xml.
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.androidbrowser"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="19" />
        <uses-permission android:name="android.permission.INTERNET"/>
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="com.example.androidbrowser.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

    Running on Nexus 7, Android 4.4 Kitkat.