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.


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.

3 comments: