Pages

Friday, October 18, 2013

Zipper

In this tutorial, you learn to create a Zipper for Android. The Zipper can be used to zip files and folders. The result of the compression is a zip file. The user also can use this app for extracting zip or jar files.

To zip a file or folder, firstly the user has to open the Zipper app. Then he/she will select the file or folder from the file chooser. By pushing the Compress button, the file or folder will be compressed in to a single zip file stored in the Zip folder of your external card.

compass app flashLight app

zip files and folders for android

To extract a zip file or jar file, the user can select the file from anywhere in Android and press the Extract button. Alternatively, the user can open Zipper app first and then select the zip file or jar file from the file chooser list. The extracted file or folder is stored in the Extract folder.

storage of the zipper in real device



Now open your Eclipse and create a new project called Zipper. In this app, we need one EditText to display the selected file or folder path and allow the user to enter the file or folder path. Two Buttons are needed. One is for extracting action and another is for compressing action. A ListView component will be used as a file chooser that displays files and folders for selection. These components are defined in the activity_main xml file that is the interface' elements resource of the MainActivity.class.

activity_main.xml file

<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"
    tools:context=".MainActivity"
    android:orientation="vertical"

     >

  <EditText
        android:id="@+id/txt_input"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:hint="@string/txt_hint"
        />
        <LinearLayout
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:orientation="horizontal"
             >
<Button
          android:id="@+id/bt_extract"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/bt_extract"
          android:onClick="extract"
          />
  <Button
          android:id="@+id/bt_compress"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/bt_compress"
          android:onClick="compress"
          />
   </LinearLayout>
  <TextView
          android:id="@+id/txt_view"
    android:layout_width="fill_parent"
          android:layout_height="wrap_content"
      />
        <ListView
            android:id="@+id/files_list"
            android:layout_width="fill_parent"
            android:layout_height="300dp"
            android:paddingBottom="5dp"
            android:paddingTop="5dp"
     
            />    

</LinearLayout>



In the activity_xml files, some string variables are used. These string variables are defined in the strings.xml file. This is the content of the strings.xml file.

strings.xml file

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Zipper</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="bt_extract">Extract</string>
    <string name="txt_hint">Type file path</string>
    <string name="bt_compress">Compress</string>
   <string name="icon_image">Icon</string>
</resources>


Again, the ListView displays both files and directories. Each item of the list contains two parts--icon and text. The icon can be file icon or folder icon. The text is the file name or folder name. The ListView must be customized so that its item can be displayed both icon and text. This can be done in two-steps process. The first step will define two components in the layout file of the list. One component is the ImageView and another is the TextView. The ImageView is for displaying the icon and the TextView displays the file name or directory name. Here is the content of the listlayout.xml file that is the layout file of the ListView.

listlayout.xml file

<?xml version="1.0" encoding="utf-8"?>
<!--  Single List Item Design -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="5dip" >

<ImageView
    android:id="@+id/icon"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:padding="5sp"
    android:contentDescription="@string/icon_image"
 />

<TextView
    android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10sp"
        android:textSize="20sp"
        android:textColor="#0000ff"
        android:textStyle="bold" >
</TextView>
</LinearLayout>


The last step will customize the ArrayAdapter class to supply icon image and text to the ListView. The ArrayAdapter object will be used as a data source of the ListView. Below is the ListAdapter class that extends the ArrayAdapter class and customize its getView method.

ListAdapterModel.java

package com.example.zipper;

import java.io.File;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;


public class ListAdapterModel extends ArrayAdapter<String>{
int groupid;
String[] names;
Context context;
String path;
public ListAdapterModel(Context context, int vg, int id, String[] names, String parentPath){
super(context,vg, id, names);
this.context=context;
groupid=vg;
this.names=names;
this.path=parentPath;
}
public View getView(int position, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        View itemView = inflater.inflate(groupid, parent, false);
        ImageView imageView = (ImageView) itemView.findViewById(R.id.icon);
        TextView textView = (TextView) itemView.findViewById(R.id.label);
        String item=names[position];
        textView.setText(item);
        File f=new File(path+"/"+item);
        if(f.isDirectory())
        imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.diricon));
        else
        imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.fileicon));
        return itemView;
}

}


Now we take a look at the MainActivity.java that defines the MainActivity class. The MainActivity class represents the main interface of the Zipper app. This is where the user interacts with the Zipper by entering file or folder path, pushing the Extract or Compress button, and select a file or folder from the file chooser.

MainActivity.java file

package com.example.zipper;

import java.io.File;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

   private String pathintent="";
   private String path="";
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //get the intent object broadcast by Android
        Intent intent=getIntent();
        String action=intent.getAction();      
        String type =intent.getType();
        //get zip file path from intent
        if(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_PICK.equals(action) && type!=""){        
        pathintent=intent.getData().getPath();
         }

    }

    protected void onStart(){
    super.onStart();
    regComponents();
    }
 
    protected void onResume(){
    super.onResume();
    //register the broadcast receiver to receive intents
    registerReceiver(receiverextract, new IntentFilter("com.example.zipper.extract"));
    registerReceiver(receivercompress, new IntentFilter("com.example.zipper.compress"));

    }
 
    protected void onPause(){
    super.onPause();
    //unregister the broadcast receivers
    unregisterReceiver(receiverextract);
    unregisterReceiver(receivercompress);

    }
    public void onBackPressed(){
    if(path.length()>1){ //up one level of directory structure
    File f=new File(path);
    path=f.getParent();
    EditText et=(EditText) findViewById(R.id.txt_input);
    et.setText(path); //update text box of path
    //listDirContents();
    }
    else{
   
    System.exit(0); //exit app
   
    }
    }

    private BroadcastReceiver receiverextract=new BroadcastReceiver(){
    public void onReceive(Context context,Intent intent){
   
        Bundle b=intent.getExtras();
        if(b!=null){
        TextView tv=(TextView)findViewById(R.id.txt_view);
        tv.setText(b.getString("BACKMESS"));
        }
    }
    };
 
    private BroadcastReceiver receivercompress=new BroadcastReceiver(){
    public void onReceive(Context context,Intent intent){
   
        Bundle b=intent.getExtras();
        if(b!=null){
        TextView tv=(TextView)findViewById(R.id.txt_view);
        tv.setText(b.getString("BACKMESS"));
        }
    }
    };

 
 
    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;
    }
 
    public void regComponents(){
EditText et=(EditText) findViewById(R.id.txt_input);
if(et!=null){
et.addTextChangedListener(new ChangeListener());
if(pathintent.length()>0)
et.setText(pathintent);
else{
path=Environment.getExternalStorageDirectory().getPath();
et.setText(path);
}
ListView lv=(ListView) findViewById(R.id.files_list);
lv.setSelector(R.drawable.selection_style);
lv.setOnItemClickListener(new ClickListener());
}
}
 
class ChangeListener implements TextWatcher{
   
    public void beforeTextChanged(CharSequence s, int start, int before, int count){
   
   
    }
   
    public void onTextChanged(CharSequence s, int start, int before, int count){
    EditText et=(EditText) findViewById(R.id.txt_input);
    path=et.getText().toString(); //capture file path    
    listDirContents();//update list
    }
   
    public void afterTextChanged(Editable ed){
   
   
    }
    }
 
    class ClickListener implements OnItemClickListener{
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
      // selected item      
            ViewGroup vg=(ViewGroup)view;
      String selectedItem = ((TextView) vg.findViewById(R.id.label)).getText().toString();
            EditText et=(EditText) findViewById(R.id.txt_input);
            path=path+"/"+selectedItem;
            et.setText(path); //update file path          
           
      }
      public void onNothingSelected(AdapterView<?> parent){
     
      }
     
     
      }  
 
 
    public void listDirContents(){
    ListView l=(ListView) findViewById(R.id.files_list);
    if(path!=null){
    try{
    File f=new File(path);
    if(f!=null){
    if(f.isDirectory()){
    String[] contents=f.list();
    if(contents.length>0){
    ListAdapterModel lm=new ListAdapterModel(this,R.layout.listlayout,R.id.label,contents,path);
    l.setAdapter(lm);
    }
    else
    {  //keep tract the parent folder of empty directory
    path=f.getParent();
    }
    }
    else{
    //keep tract the parent folder of the selected file
    path=f.getParent();
    }
    }
    }catch(Exception e){}
    }    
 
   
    }
//This method will be invoked to detect the zip and jar file types
public boolean isZipFile(String file){

boolean isZip=false;
String extension = MimeTypeMap.getFileExtensionFromUrl(file);
File f=new File(file);
if(f.isFile()){
String mimeType=MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if(mimeType!=null){
if(mimeType.endsWith("zip")) //detect zip file
isZip=true;
}
else{
if (file.endsWith(".jar")) //detect jar file
isZip=true;
}
}
        return isZip;
}


    public void extract(View view){
    TextView tv=(TextView)findViewById(R.id.txt_view);
    EditText et=(EditText)findViewById(R.id.txt_input);
    String txtpath=et.getText().toString();
    if(txtpath.length()>0){
    if(isZipFile(txtpath)){
    tv.setText("Please wait...");
    //create intent object and set it to the extracting service object
    Intent newIntent=new Intent(this,ExtractingService.class);
    newIntent.putExtra(ExtractingService.FILEPATH,txtpath);
    newIntent.putExtra(ExtractingService.DESPATH,Environment.getExternalStorageDirectory()+"/Extract");
    startService(newIntent);
    }
    else{
    showAlert("This is not a zip file.");
    }
    }
    else{
    showAlert("Please enter or select file path.");
    }

    }
 
 

    public void compress(View view){
    TextView tv=(TextView)findViewById(R.id.txt_view);
    EditText et=(EditText)findViewById(R.id.txt_input);
    String txtpath=et.getText().toString();
    if(txtpath.length()>0){
    tv.setText("Please wait...");
    //create intent object and set it to the zipping service object
    Intent newIntent=new Intent(this,ZippingService.class);
    newIntent.putExtra(ZippingService.FILEPATH,txtpath);
    newIntent.putExtra(ZippingService.DESPATH,Environment.getExternalStorageDirectory()+"/Zip");
   
    startService(newIntent);
    }
    else{
    showAlert("Please enter or select file path.");
    }

    }
    public void showAlert(String message){
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(message);
        builder.setCancelable(true);
        builder.setPositiveButton("OK", new OnClickListener(){
        public void onClick(DialogInterface dialog, int which) {
          dialog.dismiss();
          }

        });
        AlertDialog dialog = builder.create();      
        dialog.show();
   }
 
}


In the onCreate method of the MainActivity class, the getIntent method is invoked to receive the intent object broadcast by Android. The intent object contains the zip or jar file path that the user selected from anywhere on Android. This file path will be displayed in the text box. The intent-filters are used to allow the Zipper app to receive the data sent by Android system. The intent-filters are defined in the AndroidManifest.xml file. This is the content of the AndroidManifest file for the Zipper app.

AndroidManifest.xml file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.zipper"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/zipper"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity android:configChanges="orientation"          
            android:name="com.example.zipper.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.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="file"/>
            <data android:mimeType="*/*"/>
            <data android:pathPattern=".*\\.zip"/>
            <data android:host="*"/>
        </intent-filter>
        <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="file"/>
            <data android:mimeType="*/*"/>
            <data android:pathPattern=".*\\.jar"/>
            <data android:host="*"/>
        </intent-filter>
       
       
        </activity>
        <service
android:name="ExtractingService"
android:icon="@drawable/ic_launcher"
android:label="MYSERVICE">
</service>

        <service
android:name="ZippingService"
android:icon="@drawable/ic_launcher"
android:label="MYSERVICE1">
</service>      
     
    </application>

</manifest>


You might want to read WebDownloader page to learn more about intent filers.

In the onStart method, the regComponents method is called to register the EditText to the change event listener and the ListView to the item click event listener.

In the onResume method, two broadcast receivers are registered with the current context to receive the intent objects that will be sent from Intent Services. One receiver, receiverextract will receive intent object sent by the extracting intent service and another one, receivercompress receives intent sent by the compressing or zipping service. A broadcast receiver is defined by using the BroadcastReceiver class. You need to override its onReceive method to get the intent object sent by an intent service.

The extract method will be invoked when the user pushes the Extract button to extract the selected zip or jar file. This method calls the isZipFile method to detect the zip or jar file to make sure that other types of files are not extracted. As the text box is not blank and the desired file is selected, the intent object is created to wrap the selected file path and the destination folder (folder to store the extracted file). This intent object is sent to the extracting service by calling the startService method. The extracting service that will receive the intent object is called ExtractingService defined in the ExtractingService class that extends the IntentService class. Here is the content of the ExtractingService class.

ExtractingService.java file

package com.example.zipper;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;

public class ExtractingService extends IntentService {
public static final String FILEPATH="FILEPATH";
public static final String DESPATH="DESPATH";
private String feedback="";
public ExtractingService() {
super("SERVICE");
// TODO Auto-generated constructor stub
}

public void onHandleIntent(Intent intent){
//receive information sent from the MainAcivity
Bundle b=intent.getExtras();
String filepath="";
String despath="";
if(b!=null){
filepath=b.getString(FILEPATH);
despath=b.getString(DESPATH);
extractFiles(filepath,despath);

}


}

public void extractFiles(String srcfile, String despath){
ZipFile zf=null;
    try {
zf=new ZipFile(srcfile); //create  a zip file object
if(zf.size()>0){ //read through the zip file
Enumeration<ZipEntry> entries=(Enumeration<ZipEntry>) zf.entries();
while(entries.hasMoreElements()){
ZipEntry entry=entries.nextElement();
if(!entry.isDirectory() && !entry.getName().endsWith("/")){
//start extracting the files
extract(zf.getInputStream(entry),entry.getName(),despath);

}

}

}

feedback="Complete";

} catch (IOException e) {

e.printStackTrace();

}finally{
if(zf!=null)
try {
zf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//send feedback message to the broadcast receiver
if(feedback.length()<=0)
feedback="Can't not extract the file";
Intent backIntent=new Intent("com.example.zipper.extract");
backIntent.putExtra("BACKMESS", feedback);
sendBroadcast(backIntent);
}
    }
 
public void extract(InputStream is, String fname, String storeDir){

FileOutputStream fos;
File fi=new File(storeDir+File.separator+fname); //output file
File fparent=new File(fi.getParent());
fparent.mkdirs();//create parent directories for output files

try {

fos=new FileOutputStream(fi);
int content=0;
while((content=is.read())!=-1){
fos.write(content);
}
is.close();
fos.close();
} catch (Exception e) {

e.printStackTrace();
}

}

}


Immediately as the ExteractingService receives the intent object sent from the MainActivity, the extracting process begins to extract the content of the file path to store in the destination folder by invoking the extractFiles method . In this method, the ZipFile class of Java is used to read the content of the zip file. Each file or folder is presented by a ZipEntry object. You can get all ZipEntries by using the entries method of the ZipFile. The ZipEntry has the getInputStream method that returns an InputStream object in which you can get its content out. When the extracting process finishes, an intent object is created to wrap the feedback message that will be sent back to the MainActivity.

The compress method will be invoked to compress the file or folder when the user pushes the Compress button. The selected file or folder path and the destination path (folder path to store the zip file) to the ZippingService intent service. Once these information received the compression process begins by calling the doCompression method. To write content of a file or folder to a zip output file you can use the ZipOutputStream. The user might select a file or folder to compress. If the selected path is a file, the comrpessFile method will be called to read the file content and output it to the zip output file. If the selected path is a folder, the compressDir method will be called to compress the folder. The folder might contain many files and sub folders. To compress all files and sub folders in the folder, the recursive process is needed to traverse the folder hierarchy.

ZippingService.java file

package com.example.zipper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

public class ZippingService extends IntentService {
public static final String FILEPATH="FILEPATH";
public static final String DESPATH="DESPATH";

public ZippingService() {
super("SERVICE");
// TODO Auto-generated constructor stub
}

public void onHandleIntent(Intent intent){
//receive information sent from the MainAcivity
Bundle b=intent.getExtras();
String filepath="";
String despath="";
if(b!=null){
filepath=b.getString(FILEPATH);
despath=b.getString(DESPATH);
doCompression(filepath,despath);

}

}

public void doCompression(String src,String despath){
File f=new File(src);
File foutdir=new File(despath);
if(!foutdir.exists()) foutdir.mkdir();
ZipOutputStream zos=null;
try{
//create ZipOutputStream object to write to output zip file
zos=new ZipOutputStream(new FileOutputStream(foutdir.getPath()+"/"+getName(src)+".zip"));
if(f.exists()){
String path=getPath(f.getPath());
if(f.isFile()){
compressFile(f.getPath(),path,zos);
}
else{ //source is a directory
File[] files=f.listFiles();
for(File sf:files){
compressDir(sf.getPath(),path,zos);
}
}


}
else{
Log.e("Error","Source not found!");
}
zos.close();
                //send the feed back message to the MainActivity
String feedback="Complete";
Intent backIntent=new Intent("com.example.zipper.compress");
backIntent.putExtra("BACKMESS", feedback);
sendBroadcast(backIntent);

}catch(Exception e){e.printStackTrace();}

}
public String getPath(String srcpath){

String path="";
if(srcpath.endsWith(File.separator)){
path=srcpath.substring(0,srcpath.length()-1);
path=path.substring(path.lastIndexOf(File.separator)+1);
}
else
path=srcpath.substring(srcpath.lastIndexOf(File.separator)+1);
return path;
}

public String getName(String srcpath){
String fname;
fname=srcpath.substring(srcpath.lastIndexOf("/")+1);
if(fname.contains("."))
fname=fname.substring(0, fname.lastIndexOf("."));
return fname;
}
public void compressDir(String srcpath, String path, ZipOutputStream zos){
File fsrcdir=new File(srcpath);
String rpath=getPath(srcpath);
if(fsrcdir.isDirectory()){
try {

rpath=path+File.separator+rpath;
zos.putNextEntry(new ZipEntry(rpath+File.separator));
zos.closeEntry();
File[] files=fsrcdir.listFiles();
for(File f:files){
if(f.isDirectory()){
compressDir(f.getPath(),rpath,zos);
}
else{
compressFile(f.getPath(),rpath,zos);
}
}

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else{
compressFile(srcpath,path,zos);
}
}

public void compressFile(String srcfile, String path,ZipOutputStream zos){
//write a new entry to the zip file
String rpath=getPath(srcfile);
try {
FileInputStream fis=new FileInputStream(srcfile);
int content=0;
zos.putNextEntry(new ZipEntry(path+File.separator+rpath));
while((content=fis.read())!=-1){
zos.write(content);
}
zos.closeEntry();
fis.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

Now you are ready to run and test the Zipper for Android app. If you want to download the apk file from here, click the link below. If you have any questions, please write them at the comment section.

Tuesday, October 15, 2013

File Locker for Android

In this tutorial, you will learn to create a File Locker app for Android devices. The File Locker app displays a list of files that the user can select to lock or unlock. The directories also list so that the user is able to navigate from one directory to another. To lock or unlock a file, the user has to select the file and enter the password in to the password text box. Then the user will push the Lock button to lock the file or push the Unlock button to unlock the file.




When the file is locked, the locked icon will display next to the file (on the left) when its parent directory is refreshed. The file that is already locked is not allowed to lock again unless it is unlocked. To unlock the file the user must provide the correct password. It is the password that he/she uses to lock the file.

The user can choose to lock any file. The locked file will be not understandable since its content is encrypted. The encryption and decryption processes are performed on the file.The performance (speed of the processes) of the app still be maintained although the file is very large (e.g audio, video, and image files) . I will talk about this technique later in this post.

Now open your Eclipse and create a new project. The project name will be FileLocker. On the main interface, an EditText component is needed to allow the user to input the password. We need two Buttons. One is for locking the file and another one is for unlocking the file. We also need a ListView to display the list of files and directories. These components are defined in the activity_main.xml file that is the resource file of the MainActivity class. Here is its content.

activity_main.xml file

<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"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:background="@drawable/back_style"
     >

  <EditText
        android:id="@+id/txt_input"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="textPassword"    
        android:hint="@string/txt_hint"
        />
        <LinearLayout
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:orientation="horizontal"
             >
<Button
          android:id="@+id/bt_lock"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/label_lock"
          android:onClick="lockFile"
          />
  <Button
          android:id="@+id/bt_unlock"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/label_unlock"
          android:onClick="unlockFile"
          />
  </LinearLayout>
   <TextView
    android:id="@+id/txt_view"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
      />
        <ListView
            android:id="@+id/files_list"
            android:layout_width="fill_parent"
            android:layout_height="300dp"
            android:paddingBottom="5dp"
            android:paddingTop="5dp"
     
            />    

</LinearLayout>


The file that applies background style to the interface is called back_style.xml. It is saved in the drawable directory of the project. In this directory, you also need some icon images that are used in the File Locker app. You can download the content of the drawable directory from here.

back_style.xml file

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
    <item>
        <shape>
            <gradient
                android:startColor="#dfdfdf"
                android:endColor="#dfdfdf"            
                android:angle="180" />
        </shape>      
    </item>
</selector>

The string values that are used in the activity_main.xml file are defined in the strings.xml file. This is the content of the strings.xml file.

strings.xml file

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">FileLocker</string>
    <string name="action_settings">Settings</string>
   <string name="label_lock">Lock</string>
   <string name="txt_hint">Enter password</string>
<string name="label_unlock">Unlock</string>
<string name="icon_image">Icon</string>

</resources>



The ListView has its own layout file. This file defines the template for an item of the list. An item of the list contains two parts. One part is the icon. It can be file icon, directory icon, or locked icon. Another part is the file or directory name. This layout file is called listlayout.xml file stored in the layout directory.

listlayout.xml file

<?xml version="1.0" encoding="utf-8"?>
<!--  Single List Item Design -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="5dip" >

<ImageView
    android:id="@+id/icon"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:padding="5sp"
    android:contentDescription="icon_image"
 />

<TextView
    android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10sp"
        android:textSize="20sp"
        android:textColor="#0000ff"
        android:textStyle="bold" >
</TextView>
</LinearLayout>


In default the ListView component displays only text. To enable the ListView to display both images and text, you need to customize it. The ListView customization starts from defining its layout file to include two components--image and text as you see in the listlayout.xml file shown above. Another step is to extend the ArrayAdapter class so that image and text can be placed on the components. The ListAdapterModel is created to accomplish this task. The ListAdapterModel object will contain data required to display on the list.

ListAdapterModel.java file

package com.example.filelocker;

import java.io.File;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;


public class ListAdapterModel extends ArrayAdapter<String>{
int groupid;
String[] names;
Context context;
String path;
public ListAdapterModel(Context context, int vg, int id, String[] names, String parentPath){
super(context,vg, id, names);
this.context=context;
groupid=vg;
this.names=names;
this.path=parentPath;
}
public View getView(int position, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        View itemView = inflater.inflate(groupid, parent, false);
        ImageView imageView = (ImageView) itemView.findViewById(R.id.icon);
        TextView textView = (TextView) itemView.findViewById(R.id.label);
        String item=names[position];
        textView.setText(item);
        File lockedfile=new File(context.getFilesDir(),item);
        if(lockedfile.exists()){
        //set the locked icon to the file that was already locked.
        imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.locked_icon));
        }
        else{//set the directory and file icon to the unlocked file
        File f=new File(path+"/"+item);
        if(f.isDirectory())
        imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.diricon));
        else
        imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.fileicon));
        }
        return itemView;
}

}


To help us in locking and unlocking file processes, we will need a Locker class. The Locker.java file defines a class called Locker. This class contains methods that will be used in locking and unlocking file process.

Locker.java file

package com.example.filelocker;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import android.content.Context;
import android.webkit.MimeTypeMap;

class Locker{
String path;
String pwd;
Context context;
final String separator="--*****--";

Locker(Context context,String path,String pwd){
this.path=path;
this.pwd=pwd;
this.context =context;
}


public boolean isTextFile(String file){

boolean isText=false;
String extension = MimeTypeMap.getFileExtensionFromUrl(file);
   String mimeType=MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
   if(mimeType.startsWith("text/"))
    isText=true;
        return isText;
}

public void lock(){
boolean isHead=true;
boolean isBody=false;
int blockSize=0;

try{
File f=new File(path);
//get previous pwd
if(f.exists()){
byte[] ppwd=getPwd();
if(ppwd!=null){
MessageAlert.showAlert("Alreadly locked",context);
return;
}
FileInputStream fis=new FileInputStream(f);
File tempfile=new File(context.getFilesDir(),"temp.temp");
FileOutputStream fos=new FileOutputStream(tempfile);
FileChannel fc=fis.getChannel();
int pwdInt=bytearrayToInt(pwd.getBytes());
int nRead;
boolean isText=isTextFile(path);
if(isText){ //encrypting two parts of the text file
blockSize=(int)f.length()/4; //25 percent of the file content
ByteBuffer bb=ByteBuffer.allocate(blockSize);

while ( (nRead=fc.read( bb )) != -1 )
{
bb.position(0);
bb.limit(nRead);

//encrypt the head section of the file
if(isHead){
while ( bb.hasRemaining())
fos.write(bb.get()+pwdInt);
isHead=false;
isBody=true;
}
else if(isBody){
//do not decrypt the body section of the file
fos.write(bb.array());
isBody=false;
}
else{//encrypt the footer section of the file
while ( bb.hasRemaining())
fos.write(bb.get()+pwdInt);
}

bb.clear();

}
}

else{
blockSize=1024; //encrypt the first 1kb of the file for non-text file
ByteBuffer bb=ByteBuffer.allocate(blockSize);

while ( (nRead=fc.read( bb )) != -1 )
{
bb.position(0);
bb.limit(nRead);
//encrypt only the head section of the file
if(isHead){
while ( bb.hasRemaining())
fos.write(bb.get()+pwdInt);
isHead=false;

}
else{
fos.write(bb.array());
}
bb.clear();

}
}

fis.close();
fos.flush();
fos.close();
//replacing the file content
f.delete();
File lockedFile=new File(path);
copyFile(tempfile,lockedFile);
//delete the temp file
tempfile.delete();
//save the password
saveInfo(pwd,blockSize);
//make the file read only
lockedFile.setReadOnly();

}

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


public void unlock(){
boolean isHead=true;
boolean isBody=false;
int pwdread=bytearrayToInt(getPwd());
int pwdbyte=bytearrayToInt(pwd.getBytes());
if(pwdbyte==pwdread){

try{
File f=new File(path);
if(f.exists()){

FileInputStream fis=new FileInputStream(f);
File tempfile=new File(context.getFilesDir(),"temp.temp");
FileOutputStream fos=new FileOutputStream(tempfile);
FileChannel fc=fis.getChannel();
int pwdInt=bytearrayToInt(pwd.getBytes());
int blockSize=getBlockSize();
ByteBuffer bb=ByteBuffer.allocate(blockSize);
int nRead;
boolean isText=isTextFile(path);
if(isText){ //decoding two parts of the text file
while ( (nRead=fc.read( bb )) != -1 )
{
bb.position(0);
bb.limit(nRead);

//decrypt the head section of the file
if(isHead){
while ( bb.hasRemaining())
fos.write(bb.get()-pwdInt);
isHead=false;
isBody=true;
}
else if(isBody){
//do not decrypt the body section of the file
fos.write(bb.array());
isBody=false;
}
else{//decrypt the footer section of the file
while ( bb.hasRemaining())
fos.write(bb.get()-pwdInt);
}

bb.clear();

}
}

else{

while ( (nRead=fc.read( bb )) != -1 )
{
bb.position(0);
bb.limit(nRead);
//encrypting only the head section of the file
if(isHead){
while ( bb.hasRemaining())
fos.write(bb.get()-pwdInt);
isHead=false;

}
else{
fos.write(bb.array());
}
bb.clear();

}
}

fis.close();
fos.flush();
fos.close();
//Replacing the file content
f.delete();
File unlockedFile=new File(path);
unlockedFile.setWritable(true);
copyFile(tempfile,unlockedFile);
//delete the temp file
tempfile.delete();
File filepwd=new File(context.getFilesDir(),getName(path));
//delete the password
filepwd.delete();



}

}catch(IOException e){e.printStackTrace();}
}
else{
MessageAlert.showAlert("Invalid password or file is not locked.",context);
}
}

private void copyFile(File src, File dst) throws IOException
{
FileInputStream fis=new FileInputStream(src);
FileOutputStream fos=new FileOutputStream(dst);
    FileChannel inChannel =fis.getChannel();
    FileChannel outChannel = fos.getChannel();
    try
    {
        inChannel.transferTo(0, inChannel.size(), outChannel);
    }catch(IOException e){e.printStackTrace();}
    finally
    {
        if (inChannel != null){
        fis.close();
            inChannel.close();
        }
        if (outChannel != null){
        fos.close();
            outChannel.close();
        }
       
    }
}
private int bytearrayToInt(byte[] pwd){
int b=0;
if(pwd!=null)
for(byte y:pwd){
b=b+y;
}
return b;

}

private byte[] getPwd(){
byte[] b=null;
try {
File f=new File(context.getFilesDir(),getName(path));
if(f.exists()){
BufferedReader br=new BufferedReader(new FileReader(f));
String info=br.readLine();
b=info.substring(0,info.lastIndexOf(separator)).getBytes();
br.close();
}
} catch(Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return b;

}

private int getBlockSize(){
int size=0;
try {
File f=new File(context.getFilesDir(),getName(path));
if(f.exists()){
BufferedReader br=new BufferedReader(new FileReader(f));
String info=br.readLine();
size=Integer.valueOf(info.substring(info.lastIndexOf(separator)+separator.length()));
br.close();
}
} catch(Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return size;

}

private void saveInfo(String pwd,int blockSize){
try {
String fileName=getName(path);
File f=new File(context.getFilesDir(),fileName);
BufferedWriter bw=new BufferedWriter(new FileWriter(f));
String info=pwd+separator+blockSize;
bw.write(info);
bw.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

private String getName(String path){
return(path.substring(path.lastIndexOf("/")+1));
}



 
}


The constructor of the Locker class will receive the selected file path, password, and context sent from the MainActivity class. These information are used in the locking and unlocking file processes.
The isTextFile method will be called to check whether the selected file is a text file. To improve the performance of locking and unlocking processes, text file and other types files will be encrypted differently .

The lock method locks the selected file by encrypting it so that it is not understandable. If the file is a text file, the two parts (header and footer) of the file will be encrypted. The header part will be 25 percent of file content. The body part is also 25 percent of the file content. The rest is the footer part. The body part will not be encrypted. You can encrypt all content of the file. However, its performance will degrade when the file is large. Another thing that we try to do in improving the locking or unlocking process' performance is using the FileChannel with ByteBuffer to read block of bytes from the source file and write this block to the temporary file. The content of the temporary file will replace the content of the source file. Reading a block of file that contains many bytes at a time and reading one byte at a time are different. Reading each block of the file until all blocks are read is faster than reading one byte one until all content of the file is read. In this locking or unlocking text file process, each block that occupies 25 percent of the file content will be read a a time.

Header (25% or 1/4)
Body (25% or 1/4)
Footer (50% or 1/2)

Encrypting the file content is simple. In this FileLocker app, we encrypt the file content by modifying the byte data of the file. For the part of content to be encrypted, its every byte is added to the sum of bytes generated from the password text.

fos.write(bb.get()+pwdInt);

If the selected file is not a text file, the locking or unlocking process is performed only at the header section of the file. So large audio, video, or image files can be locked or unlocked very fast. The size of header block is 1024 bytes. Each block of the file of this size will be read from the source file that is not the text file.

The unlock method will be called to unlock the locked file by decrypting parts of the file that were encrypted in the locking process. In the unlocking process every byte of each of the file content is subtracted by the sum of bytes previously added to every byte.

fos.write(bb.get()-pwdInt);

The copyFile method is invoked by the lock and unlock method to copy the content of the temparary file to the destination file in file content replacement process.

The bytearrayToInt sums all bytes in the input array of bytes. This method is called to sum all bytes generated from the password text.

The getPwd method converts the password text in to an array of bytes.

The getBlockSize method is invoked by the unlocking process to read the size of the encrypted block from the file that is saved in the locking process. So the unlocking process knows the block of bytes to be read and decrypted. This file stores the password text and the encrypted block size.

The saveInfo method is invoked by the locking process to save the password text and the block size to a file. The password text and the block size is stored in the file in the form as shown below.

password--*****--size

The last method of the Locker class is getName. This method simply returns the file name of the selected file path.

Now we take a look at the MainActivity class that is in the MainActivity.java file.
MainActivity.java file

package com.example.filelocker;
import java.io.File;

import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;

import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {


   private String path="";
   private String selectedFile="";
   private Context context;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.context=this;
 
    }

    protected void onStart(){
    super.onStart();
    ListView lv=(ListView) findViewById(R.id.files_list);
if(lv!=null){
lv.setSelector(R.drawable.selection_style);
lv.setOnItemClickListener(new ClickListener());
}
path="/mnt";
listDirContents();
    }
 
    public void onBackPressed(){
    if(path.length()>1){ //up one level of directory structure
    File f=new File(path);
    path=f.getParent();
    listDirContents();
    }
    else{
    refreshThumbnails();
    System.exit(0); //exit app
   
    }
    }
 
 
    private void refreshThumbnails(){
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
}
    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;
    }

 
    private class ClickListener implements OnItemClickListener{
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
      //selected item      
            ViewGroup vg=(ViewGroup)view;
      String selectedItem = ((TextView) vg.findViewById(R.id.label)).getText().toString();
            path=path+"/"+selectedItem;
            //et.setText(path);          
            listDirContents();
      }
     
     
      }
 
 
 
    private void listDirContents(){
    ListView l=(ListView) findViewById(R.id.files_list);
    if(path!=null){
    try{
    File f=new File(path);
    if(f!=null){
    if(f.isDirectory()){
    String[] contents=f.list();
    if(contents.length>0){
    //create the data source for the list
    ListAdapterModel lm=new ListAdapterModel(this,R.layout.listlayout,R.id.label,contents,path);
    //supply the data source to the list so that they are ready to display
    l.setAdapter(lm);
    }
    else
    {
    //keep track the parent directory of empty directory
    path=f.getParent();
    }
    }
    else{
    //capture the selected file path
    selectedFile=path;
    //keep track the parent directory of the selected file
    path=f.getParent();
   
    }
    }
    }catch(Exception e){}
    }    
 
   
    }
 
    public void lockFile(View view){
    EditText txtpwd=(EditText)findViewById(R.id.txt_input);
String pwd=txtpwd.getText().toString();
if(pwd.length()>0){

if(selectedFile.length()>0){
BackTaskLock btlock=new BackTaskLock();
btlock.execute(pwd,null,null);

}
else{
MessageAlert.showAlert("Please a select a file to lock",context);
}
}
else{
MessageAlert.showAlert("Please enter password",context);
}
    }
 
    public void startLock(String pwd){
    Locker locker=new Locker(context,selectedFile,pwd);
locker.lock();
    }

    public void unlockFile(View view){
    EditText txtpwd=(EditText)findViewById(R.id.txt_input);
String pwd=txtpwd.getText().toString();
if(pwd.length()>0){

if(selectedFile.length()>0){

BackTaskUnlock btunlock=new BackTaskUnlock();
btunlock.execute(pwd,null,null);
   
}
else{
MessageAlert.showAlert("Please select a file to unlock",context);
}
}
else{
MessageAlert.showAlert("Please enter password",context);
}

    }
 
    public void startUnlock(String pwd){
    Locker locker=new Locker(context,selectedFile,pwd);
locker.unlock();
    }
 
    private class BackTaskLock extends AsyncTask<String,Void,Void>{
    ProgressDialog pd;
    protected void onPreExecute(){
super.onPreExecute();
//show process dialog
pd = new ProgressDialog(context);
pd.setTitle("Locking the file");
pd.setMessage("Please wait.");
pd.setCancelable(true);
pd.setIndeterminate(true);
pd.show();


}
protected Void doInBackground(String...params){    
try{

startLock(params[0]);

}catch(Exception e){
pd.dismiss();   //close the dialog if error occurs
}
return null;

}
protected void onPostExecute(Void result){
pd.dismiss();
}


}
 
    private class BackTaskUnlock extends AsyncTask<String,Void,Void>{
    ProgressDialog pd;
    protected void onPreExecute(){
super.onPreExecute();
//show process dialog
pd = new ProgressDialog(context);
pd.setTitle("UnLocking the file");
pd.setMessage("Please wait.");
pd.setCancelable(true);
pd.setIndeterminate(true);
pd.show();


}
protected Void doInBackground(String...params){    
try{

startUnlock(params[0]);

}catch(Exception e){
pd.dismiss();   //close the dialog if error occurs
}
return null;

}
protected void onPostExecute(Void result){
pd.dismiss();
}


}

 
}


In the onStart method of MainActivity class, the ListView component is registered to the item click event. Immediately, the listDirContents method is invoked to display files and directories in the /mnt directory. The selection style of the list is defined by the selection_style.xml file.

selection_style.xml file.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
    <item>
        <shape>
            <gradient
                android:startColor="#dfdfdf"
                android:endColor="#dfdfdf"            
                android:angle="180" />
        </shape>      
    </item>
</selector>

The onBackPressed method is invoked every time the user presses the Back button on the  device. It will move back one level of the directory structure until the root directory is reached.

The refreshThumbnails method is invoked in case that the root directory is reached when the user presses the Back button. The external card will be refreshed at that time. This is to make sure that any change that is made to the media files ( audio, audio, or video files) will change the thumbnails of the files.

The lockFile method will be invoked when the user pushes the Lock button. The locking process might take long time. So it is placed in the background by using the AsyncTask class. In the doInBackground method of the AsyncTask class, the lock method of the Locker class is called from the startLock method to perform the locking process. The progress dialog displays to inform the user to wait until the locking process completes.



The unlockFile method is invoked when the user pushes the Unlock button. This process is also placed in background by using the AsyncTask. This method will subsequently invokes the unlock method of the Locker class to unlock the file. The progress dialog displays to inform the user to wait until the unlocking process completes.



When the user touches the Lock or Unlock button without entering the password or selecting the file, the alert dialog displays to inform the user about this action. Since the code to display the dialog is used in different classes (MainActivity and Locker class ), it is defined in a separate class called MessageAlert. Below is the content of the MessageAlert class.

MessageAlert.java file

package com.example.filelocker;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;

public class MessageAlert {
//This method will be invoked to display alert dialog
    public static void showAlert(String message,Context context){
   
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage(message);
        builder.setCancelable(true);
        builder.setPositiveButton("OK", new OnClickListener(){
        public void onClick(DialogInterface dialog, int which) {
          dialog.dismiss();
          }

        });
        AlertDialog dialog = builder.create();      
        dialog.show();
   
    }
}


Before running the FileLocker app, you will need to allow it to use the external storage by adding the below code to the AndroidManifest.xml file.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Download the apk file of the FileLocker app

nightmode  app Digital clock tdve wallpaper app