FlashAirへのアップロード

このチュートリアルでは upload.cgiを使用して、CGI経由でFlashAirにファイルをアップロード(書き込み)する方法について学びます。
使用するCGIは upload.cgi です。
このチュートリアルは別項の Android Tutorial 4: サムネイルの表示 に基づいています。


概要

FlashAirに保存されている画像を、日付別にタイトル&メモをつけて管理できるようにします。

注意: 本機能を誤って使用すると、FlashAirのファイルシステムを破壊してデータを失う恐れがあります。PCなどのSDメモリカードホスト機器はSDメモリカードの内容(FAT)をキャッシュしている場合がありますが、本CGIで行った変更をSDメモリカードホスト機器が認識する方法がありません。そのため、本CGIとSDメモリカードホスト機器から同時に変更を行うとFAT不整合が起こる可能性があります。 CGIからの書き込み後は必ずカードをいったん抜いて再挿入するなどしてSDメモリカードホスト機器に再認識させてください。

コンテンツのリストに Date List ボタンを配置します。

This image shows the content list

Date List ボタンをタップすると日付別のリストが表示されます。

This image shows an updated content list

日付をタップするとその日付の操作画面が表示されます。

This image shows the new image in an image view

アプリケーションを作成するにあたって、次のファイルを作成します。

  • MainActivity.java
  • activity_main.xml
  • ImageViewActivity.java
  • activity_image_view.xml
  • DateListActivity.java
  • activity_date_list.xml
  • list_view_item.xml
  • MemoEditActivity.java
  • activity_memo_edit.xml
  • grid_view_item.xml
  • FlashAirRequest.java

重要: Android application は、デフォルトの状態ではインターネットにアクセスすることができません。
そのため、設定ファイル AndroidManifest.xml を変更し、権限を与える必要があります。
パス: [Project_Folder]/AndroidManifest.xml AndroidManifest.xml に以下のコードを追加してください。

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

画面レイアウトの作成

strings.xml修正

strings.xmlは以下のように記述してください。
このファイルは、values フォルダに配置されています。
パス: [Project_Folder]/res/values/strings.xml

    <string name="app_name">android_tutorial_07</string>
    <string name="action_settings">Settings</string>
    <string name="title_activity_image_view">ImageViewActivity</string>
    <string name="date_list">Date List</string>
    <string name="back">Back</string>
    <string name="directory_name">Directory Name</string>
    <string name="view_image">view image</string>
    <string name="image_would_be_here">image would be here</string>
    <string name="date">Date</string>
    <string name="title">Title</string>
    <string name="title_">Title:</string>
    <string name="memo">Memo</string>
    <string name="memo_">Memo:</string>    
    <string name="save">Save</string>
    <string name="grid_view_image">grid_view_image</string>

コンテンツリストのレイアウト作成

activity_main.xml を記述して、レイアウトを決定します。
このファイルは、layout フォルダに配置されています。
パス: [Project_Folder]/res/layout/activity_main.xml

activity_main.xmlAndroid Tutorial 3: コンテンツのダウンロードと同じ状態のレイアウトにボタンを1つ追加します。

activity_main.xml に以下の内容を追加してください。

activity_main.xml
    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="@string/date_list"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

イメージビューのレイアウト作成

次に、 activity_image_view.xml を記述して、レイアウトを決定します。
このファイルは、layout フォルダに配置されています。
パス: [Project_Folder]/res/layout/activity_image_view.xml

activity_image_view.xmlAndroid Tutorial 3: コンテンツのダウンロードと同じになります。
実装の説明についてはそちらを参照してください。

日付一覧のレイアウト作成

次に、 activity_date_list.xmllist_view_item.xml を記述して、レイアウトを決定します。
list_view_item.xml は、リストの1行分のレイアウトです。
これらのファイルは、layout フォルダに配置されています。
パス: [Project_Folder]/res/layout/activity_date_list.xml [Project_Folder]/res/layout/list_view_item.xml

activity_date_list.xml に以下のように記述してください。

activity_date_list.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        tools:context=".DateListActivity" >

        <Button
            android:id="@+id/button1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:cacheColorHint="#00000000"
            android:text="@string/back"
            android:textColor="@android:color/white"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:scaleType="centerInside"
            android:text="@string/directory_name"
            android:textAlignment="center"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <ListView
            android:id="@+id/listView1"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:cacheColorHint="#00000000"
            android:clickable="true"
            android:headerDividersEnabled="false" />

    </LinearLayout>

list_view_item.xml に以下のように記述してください。

list_view_item.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">     
       <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/date" />

       <TextView
           android:id="@+id/textView2"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@string/title" />
    </LinearLayout>

操作画面のレイアウト作成

次に、 activity_memo_edit.xmlgrid_view_item.xml を記述して、レイアウトを決定します。
grid_view_item.xml は、サムネイルの表示に使用します。
これらのファイルは、layout フォルダに配置されています。
パス: [Project_Folder]/res/layout/activity_memo_edit.xml [Project_Folder]/res/layout/grid_view_item.xml

activity_memo_edit.xml に以下のように記述してください。

activity_memo_edit.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        tools:context=".MemoEditActivity" >

        <Button
            android:id="@+id/button1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:cacheColorHint="#00000000"
            android:text="@string/back"
            android:textColor="@android:color/white"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:scaleType="centerInside"
            android:text="@string/directory_name"
            android:textAlignment="center"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="152dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:gravity="center"
            android:hint="@string/date"
            android:paddingLeft="10dp"
            android:textSize="18sp" />

        <GridView
            android:id="@+id/gridView1"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.01"
            android:horizontalSpacing="10dp"
            android:numColumns="5"
            android:verticalSpacing="10dp" >
        </GridView>

        <TextView
            android:id="@+id/TextView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"        
            android:layout_marginTop="12dp"
            android:paddingLeft="10dp"
            android:text="@string/title_"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/editTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/title"
            android:inputType="text"
            android:textStyle="italic" >
        </EditText>

        <TextView
            android:id="@+id/TextView02"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"        
            android:layout_marginTop="20dp"
            android:paddingLeft="10dp"
            android:text="@string/memo_"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/editMemo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/memo"
            android:inputType="text"
            android:textStyle="italic" />

        <Button
            android:id="@+id/button2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:cacheColorHint="#00000000"
            android:text="@string/save"
            android:textColor="@android:color/white"
            android:textSize="20sp" />

    </LinearLayout>

grid_view_item.xml に以下のように記述してください。

grid_view_item.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">   

        <ImageView  
            android:id="@+id/imageView1"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:contentDescription="@string/grid_view_image"        
           />  
    </LinearLayout>

コードの作成

コンテンツリストの作成

さて、 MainActivity.java を修正します。
Android Tutorial 4: サムネイルの表示の状態に、Date List ボタンをタップした際の処理を追加します。
またここでは画像ファイルのみを対象とするので、絞り込み条件の追加等も行います。

初期化

Android Tutorial 4: サムネイルの表示MainActivity.java をコピーし、それを新しい MainActivity.java として使用してください。

メンバ変数の追加と onCreate(Bundle savedInstanceState) 関数を下記のように置き換えます。

MainActivity.java (1)
    public class MainActivity extends Activity implements AdapterView.OnItemClickListener {

        ListView listView;
        ImageView imageView;
        TextView currentDirText;
        TextView numFilesText;
        Button backButton;
        Button datelistButton;
        String rootDir = "DCIM";
        String directoryName = rootDir; // Initialize to rootDirectory
        SimpleAdapter listAdapter;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            try {
                // Set buttons
                backButton = (Button)findViewById(R.id.button1);
                getWindow().setTitleColor(Color.rgb(65, 183, 216));
                backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), 
                                                            PorterDuff.Mode.SRC_IN);
                backButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(directoryName.equals(rootDir)) {
                            listRootDirectory();
                        }
                        else {
                            int index = directoryName.lastIndexOf("/");
                            directoryName = directoryName.substring(0, index);
                            listDirectory(directoryName);
                        }
                    }
                });
                backButton.setEnabled(false); // Disable in root directory

                datelistButton = (Button)findViewById(R.id.button2);
                getWindow().setTitleColor(Color.rgb(65, 183, 216));
                datelistButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), 
                                                                PorterDuff.Mode.SRC_IN);
                datelistButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                         Intent dateList = new Intent(getBaseContext(), DateListActivity.class);     
                         dateList.putExtra("dir", currentDirText.getText());
                         MainActivity.this.startActivity(dateList);        
                    }
                });

                listRootDirectory();
            }
            catch(Exception e) {
                Log.e("ERROR", "ERROR: " + e.toString());
                e.printStackTrace();
            }
        }

作成済みの MainActivity に Date List ボタンのためのコードを追加します。

  • 38-49行目
    Date List ボタンをタップした際、日付一覧画面へ遷移するように設定しています。

残りの MainActivity.java はそのまま使用します。

対象ファイルの絞り込み

今回は画像ファイルのみを対象としたいので、 listDirectory() 内のファイル名を取得する部分で以下のように条件を追加します。

MainActivity.java (2)
                if( (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpg")) 
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpe")) 
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".png")) ) {
                    // Image file
                    fileNames.add(allFiles[i]);
                }

再表示時の設定

次の画面から戻ってきた場合など、画面が再表示された際にもリストが更新されるように設定しておきます。

MainActivity.java (3)
        @Override
        public void onRestart(){
            super.onRestart();
            listDirectory(directoryName);
        }

画像表示画面の作成

class ImageViewActivityAndroid Tutorial 3: コンテンツのダウンロードと同じです。
実装の詳細についてはこのチュートリアルを参照してください。

日付一覧の作成

初期化

リストの項目をクリックした場合の処理を記述するため、クラス宣言にOnClickListenerを追加します。

DateListActivity.java (1)
    public class DateListActivity extends Activity implements AdapterView.OnItemClickListener {

ビューの宣言、その他のクラス変数の宣言や画面の初期設定を行います。 また、 onCreate(Bundle savedInstanceState) をオーバーライドし、アクティビティクラスの初期化も行います。
リストの初期設定や一つ前の画面へ戻るための Buttonのクリックリスナーも設定します。

DateListActivity.java (2)
        ListView listView;
        ImageView imageView;
        TextView currentDirText;
        Button backButton;
        String rootDir = "DCIM";
        String directoryName;
        SimpleAdapter listAdapter;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_date_list);
            try {
                Bundle extrasData = getIntent().getExtras();
                directoryName = extrasData.getString("dir");
                if(!directoryName.equals(rootDir)) {
                    int index = directoryName.lastIndexOf("/");
                    directoryName = directoryName.substring(0, index);
                }    

                // Set buttons
                backButton = (Button)findViewById(R.id.button1);
                getWindow().setTitleColor(Color.rgb(65, 183, 216));
                backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), 
                                                            PorterDuff.Mode.SRC_IN);
                backButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        DateListActivity.this.finish(); // Go back to Get screen
                    }
                });            
                listDirectory(directoryName);
            }
            catch(Exception e) {
                Log.e("ERROR", "ERROR: " + e.toString());
                e.printStackTrace();
            }
        }
  • 22-31行目
    ユーザーがButton をクリックした際、一つ前の画面へ戻るように設定しています。

リストの作成

基本的には command.cgiを使用して、コンテンツリストを取得するのと同様の処理になります。
ファイル名に変わり、日付を重複しないように取得しています。

DateListActivity.java (3)
        public void listRootDirectory() {
            directoryName = rootDir;
            listDirectory(directoryName);
        }

        public void listDirectory(String dir) {
            // Prepare command directory path
            currentDirText = (TextView)findViewById(R.id.textView1);
            currentDirText.setText(dir + "/");

            // Fetch list of items in directory and display in a ListView
            new AsyncTask<String, Void, ListAdapter>(){
                @Override
                protected ListAdapter doInBackground(String... params) {
                    String dir = params[0];

                    String cmddir = "/" + dir;
                    ArrayList <NameValuePair> httpParams = new  ArrayList <NameValuePair> ();
                    httpParams.add(new BasicNameValuePair("DIR", cmddir));
                    cmddir = URLEncodedUtils.format (httpParams, "UTF-8" );

                    Set <Integer> dates = new HashSet <Integer>();                
                    String files = FlashAirRequest
                                .getString("http://flashair/command.cgi?op=100&" + cmddir);
                    String[] allFiles = files.split("([,\n])"); // split by newline or comma
                    for(int i = 2; i < allFiles.length; i= i + 6) {
                      if(allFiles[i].contains(".")) {
                        if( (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpg"))
                        || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                        || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpe"))
                        || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".png"))){
                            // Image file
                            Integer date = Integer.parseInt(allFiles[i+3]);
                            dates.add(date);                          
                        }
                      }
                    }                    

                    // Get Title
                    ArrayList<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
                    for (Integer date : dates) {
                        Map<String, Object> entry = new HashMap<String, Object>();                    
                        String dataStr = FlashAirRequest.getString("http://flashair/" 
                                                            + dir + "/" + getDate(date, "") + ".txt");
                        String[] dataStrAry = dataStr.split("([\n])"); // split by newline or comma
                        entry.put("date", getDate(date, "/"));
                        if (dataStrAry.length >= 2) {
                            entry.put("title", dataStrAry[0]);
                        }                    
                        data.add(entry);
                    }

                   // Set the file list to a widget
                    listAdapter = new SimpleAdapter(DateListActivity.this,
                            data,
                            R.layout.list_view_item,
                            new String[]{"date", "title"},
                            new int[]{R.id.textView1, R.id.textView2});
                    return listAdapter;
                }
                @Override
                protected void onPostExecute(ListAdapter listAdapter) {
                    listView = (ListView)findViewById(R.id.listView1);
                    ColorDrawable divcolor = new ColorDrawable(Color.rgb(17, 19, 58));
                    listView.setDivider(divcolor);
                    listView.setDividerHeight(1);
                    listView.setAdapter(listAdapter);
                    listView.setOnItemClickListener(DateListActivity.this);                
                }
            }.execute(dir);
  • 22-37行目
    各ファイルの日付を取得しています。

  • 40-51行目
    取得した日付をもとにパスを作成し、ファイルをダウンロードします。
    タイトルは、 YYYYMMDD.txt という形式で画像と同じフォルダに保存され、このファイルには1行目にタイトル、2行目にメモの情報が保存されています。
    getDate() は、command.cgiop=100 で返却された日付形式(16ビット整数を10進数表記)から、ファイル名用の日付文字列(YYYYMMDD)を生成する関数です。

  • 54-58行目
    レイアウトに作成済みの list_view_item.xml を設定し、取得した日付、タイトル、メモも設定しています。
    これらは、64行目でListView にバインドされます。

  • 63-68行目
    ListViewを作成しています。
    作成済みのlistAdapter 設定が設定され、日付とタイトルが view にバインドされています。
    作成したViewをレイアウトに設定しています。

クリックリスナーの設定

Android Tutorial 4: サムネイルの表示 と同様の設定を行います。
日付をクリックした場合に、選択した日付の操作画面を表示します。

DateListActivity.java (4)
        @Override
        public void onItemClick(AdapterView<?> l, View v, int position, long id) {
            Object item = l.getItemAtPosition(position); 
            if(item instanceof Map<?, ?>) {
                Map<String, Object> mapItem = (Map<String, Object>) item;
                Object selectDate = mapItem.get("date");
                // Next button to start new intent to allow day detail
                Intent memoEdit = new Intent(getBaseContext(), MemoEditActivity.class);     
                memoEdit.putExtra("date", selectDate.toString());
                memoEdit.putExtra("dir", directoryName);    
                DateListActivity.this.startActivity(memoEdit);        
            }
        }
  • 4-6行目
    Map オブジェクトを使用して、ListView で選択された日付を取得しています。

再表示時の設定

次の画面から戻ってきた場合など、画面が再表示された際にもリストが更新されるように設定しておきます。

DateListActivity.java (5)
        @Override
        public void onRestart(){
            super.onRestart();
            listDirectory(directoryName);
        }

ファイル名用日付文字列の取得

前述した、16ビット整数を10進数表記の日付形式から、ファイル名用の文字列(YYYYMMDD)を生成する関数です。

DateListActivity.java (6)
        public String getDate(Integer date, String sep) {
            return  String.format("%04d", ((date >> 9) & 0x1FF)+1980)+sep+
                    String.format("%02d", (date >> 5)  & 0xF)+sep+
                    String.format("%02d", date & 0x1F);
        }
    } // End DateListActivity class

操作画面の作成

初期化

ビューの宣言、その他のクラス変数の宣言や画面の初期設定を行います。
また、 onCreate(Bundle savedInstanceState) をオーバーライドし、アクティビティクラスの初期化も行います。
リストの初期設定や Button のクリックリスナーも設定します。

MemoEditActivity.java (1)
    public class MemoEditActivity extends Activity {
        TextView dateText;
        TextView currentDirText;
        Button backButton;
        Button saveButton;
        EditText titleField;
        EditText memoField;
        GridView gridView;
        String newTitle = "";
        String newMemo = "";
        String date;
        String rootDir = "DCIM";
        String directoryName; 
        ArrayList<String> fileNamelist;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_memo_edit);

            Bundle extrasData = getIntent().getExtras();
            date = extrasData.getString("date");
            directoryName = extrasData.getString("dir");

            getWindow().setTitleColor(Color.rgb(65, 183, 216));
            // Set backButton
            backButton = (Button) findViewById(R.id.button1);
            backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
            backButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    MemoEditActivity.this.finish(); // Go back
                }
            });

            // Set saveButton
            saveButton = (Button) findViewById(R.id.button2);
            saveButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
            saveButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getInput();
                    saveNewTitleMemo();
                }
            });

            // Set titleField and memoField
            titleField = (EditText) findViewById(R.id.editTitle);
            titleField.setHintTextColor(Color.rgb(65, 183, 216));
            memoField = (EditText) findViewById(R.id.editMemo);
            memoField.setHintTextColor(Color.rgb(65, 183, 216));

            // Set dateText
            dateText = (TextView) findViewById(R.id.textView2);
            dateText.setText(date);

            // Get title and memo
            new AsyncTask<String, Void, ArrayList<String>>() {
                @Override
                protected ArrayList<String> doInBackground(String... params) {
                    String dir = params[0];
                    ArrayList<String> rtnAry = new ArrayList<String>();
                    String files = FlashAirRequest.getString("http://flashair/" 
                                                + dir + "/" + date.replaceAll("/", "") + ".txt");
                    String[] allFiles = files.split("([\n])"); // split by newline or comma
                    if (allFiles.length >= 2) {
                        // File
                        rtnAry.add(allFiles[0]);
                        rtnAry.add(allFiles[1]);
                    }
                    return rtnAry;
                }

                @Override
                protected void onPostExecute(ArrayList<String> strary) {
                    // Set the data to a TextView
                    titleField = (EditText) findViewById(R.id.editTitle);
                    memoField = (EditText) findViewById(R.id.editMemo);

                    if (strary.size() > 0) {
                        titleField.setText(strary.get(0));
                        memoField.setText(strary.get(1));
                    } else {
                        titleField.setText("");
                        memoField.setText("");
                    }
                }
            }.execute(directoryName);

            // Set gridView
            gridView = (GridView) findViewById(R.id.gridView1);
            gridView.setOnItemClickListener(new OnItemClickListener() {
                public void onItemClick(AdapterView<?> parent, View v,
                            int position, long id) {
                    Object item = parent.getItemAtPosition(position); 
                    if (item instanceof Map<?, ?>) {
                        Map<String, Object> mapItem = (Map<String, Object>) item;
                        Object downloadFile = mapItem.get("fname");
                        if ((downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpg"))
                        || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                        || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpe"))
                        || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".png"))) 
                        {
                            // Image file, download using ImageViewActivity
                            Intent viewImageIntent = new Intent(MemoEditActivity.this, 
                                                                    ImageViewActivity.class);
                            viewImageIntent.putExtra("downloadFile",downloadFile.toString());
                            viewImageIntent.putExtra("directoryName", directoryName);
                            MemoEditActivity.this.startActivity(viewImageIntent);
                        } // Not an image file, do nothing
                    }
                }
            });
            listDirectory(directoryName);
        }
  • 37-45行目
    save ボタン をタップした際、保存処理が実行されるように設定しています。

  • 58-88行目
    前画面で選択された日付の、タイトル&メモを取得し画面に設定しています。

  • 91-113行目
    ファイルをダウンロードし、ダウンロードした画像を表示しした画面を表示します。

サムネイル表示

基本的には今まで行ってきたサムネイルの取得方法と同様になります。
ここでは、 ListView ではなく、 GridView を使用してサムネイルを表示しています。

MemoEditActivity.java (2)
        public void listDirectory(String dir) {
            // Prepare command directory path
            currentDirText = (TextView)findViewById(R.id.textView1);
            currentDirText.setText(dir + "/");        

            final ProgressDialog waitDialog;        
            // Setting ProgressDialog
            waitDialog = new ProgressDialog(this);
            waitDialog.setMessage("Now downloading...");
            waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            waitDialog.show();          

            ArrayList<NameValuePair> httpParams = new ArrayList<NameValuePair>();
            httpParams.add(new BasicNameValuePair("DIR", dir));
            dir = URLEncodedUtils.format(httpParams, "UTF-8");
            // Fetch list of items in directory and display in a ListView
            new AsyncTask<String, Void, ListAdapter>() {
                @Override
                protected ListAdapter doInBackground(String... params) {
                    String dir = params[0];
                    ArrayList<String> fileNames = new ArrayList<String>();
                    fileNamelist = new ArrayList<String>();
                    String files = FlashAirRequest
                            .getString("http://flashair/command.cgi?op=100&" + dir);
                    String[] allFiles = files.split("([,\n])"); // split by newline or comma
                    for (int i = 2; i < allFiles.length; i = i + 6) {
                      if (allFiles[i].contains(".") && allFiles[i+3].contains(getDate16(date))) {
                        if( (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpg")) 
                        || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                        || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpe")) 
                        || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".png"))){
                            // Image file
                            fileNames.add(allFiles[i]);
                        }
                      }
                    }

                    // Get thumbnails
                    ArrayList<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
                    for (int i = 0; i < fileNames.size(); i++) {
                        String url = "";
                        url = "http://flashair/thumbnail.cgi?" + directoryName + "/" + fileNames.get(i);
                        Map<String, Object> entry = new HashMap<String, Object>();
                        Bitmap thumbnail = null;
                        BitmapDrawable drawnIcon = null;
                        if ((url.toLowerCase(Locale.getDefault()).endsWith(".jpg")) 
                            || (url.toLowerCase(Locale.getDefault()).endsWith(".jpeg"))) {
                            thumbnail = FlashAirRequest.getBitmap(url);
                            drawnIcon = new BitmapDrawable(getResources(), thumbnail);
                        }
                        if(thumbnail == null) {
                            entry.put("thmb", R.drawable.ic_launcher);
                        }
                        else {
                            entry.put("thmb", drawnIcon);
                        }
                        entry.put("fname", fileNames.get(i)); // Put file name onto the map
                        data.add(entry);                    
                    }

                    // Set the file list to a widget
                    SimpleAdapter listAdapter = new SimpleAdapter(
                            MemoEditActivity.this, data, R.layout.grid_view_item,
                            new String[] { "thmb", "fname" }, 
                            new int[] {R.id.imageView1, android.R.id.text1 });
                    listAdapter.setViewBinder(new CustomViewBinder());

                    return listAdapter;
                }

                @Override
                protected void onPostExecute(ListAdapter adapter) {
                    waitDialog.dismiss();
                    gridView.setAdapter(adapter);
                }
            }.execute(dir);
        }
  • 20-36行目
    対象となっている日付のファイル名を取得しています。
    getDate16() は、対象となっている日付文字列(YYYY/MM/DD)から、16ビット整数を10進数表記した日付形式の日付文字列を生成する関数です。

  • 62-66行目
    レイアウトに作成済みの grid_view_item.xml を設定し、取得したサムネイルとファイル名も設定しています。 これらは、68行目でGridView にバインドされます。

  • 72-75行目
    日付とタイトルとメモが view にバインドされています。
    作成したViewをレイアウトに設定しています。

ViewBinderの作成

カスタムの ViewBinder を作成します。
GridView を使用してサムネイルを表示するのに使用します。

MemoEditActivity.java (3)
        class CustomViewBinder implements ViewBinder {
            @Override
            public boolean setViewValue(View view, Object obj, String text) {
                if((view instanceof ImageView) && (obj instanceof Drawable)) {
                    ImageView imageView = (ImageView) view;
                    BitmapDrawable thumbnail = (BitmapDrawable) obj;
                    imageView.setImageDrawable((Drawable)thumbnail);
                    return true;
                }
                return false;
            }
        }

アップロード

タイトル&メモを保存するために、以下のコマンドを使用します。

  • アップロードは、upload.cgi を使用して以下の手順で行います。
    1. CONFIGファイルに、UPLOAD=1を書き込む
    2. FlashAirを再起動する
    3. FlashAirに無線LAN接続する
    4. WRITEPROTECTコマンドで、SDメモリカードホスト機器からの書き込みを禁止する
    5. UPDIRコマンドで、アップロード先フォルダを設定する
    6. FTIMEコマンドで、ファイルの作成日時を設定する
    7. upload.cgiにファイルをPOSTし、ファイルをアップロードする

upload.cgi をPOSTする upload() を前項 Android Tutorial 3: コンテンツのダウンロード で作成した FlashAirRequest.java に追加します。

FlashAirRequest.java
        static public String upload(String command, String filename, String saveString) {            
            String result = "";
            final String boundary = "========================";
            try {
                URL url = new URL(command);
                HttpURLConnection httpUrlCon = (HttpURLConnection)url.openConnection();
                httpUrlCon.setDoInput(true);
                httpUrlCon.setDoOutput(true);
                httpUrlCon.setUseCaches(false);
                httpUrlCon.setRequestMethod("POST");    
                httpUrlCon.setRequestProperty("Charset", "UTF-8");
                httpUrlCon.setRequestProperty("Content-Type", 
                                                "multipart/form-data;boundary="+ boundary);
                DataOutputStream ds = new DataOutputStream(httpUrlCon.getOutputStream());
                ds.writeBytes("--" + boundary + "\r\n");
                ds.writeBytes("Content-Disposition: form-data; name=\"upload.cgi\";
                                filename=\"" + filename +"\""+"\r\n");
                ds.writeBytes( "\r\n" );               
                ds.write(saveString.getBytes("UTF-8"));               
                ds.writeBytes( "\r\n" );     
                ds.writeBytes("--" + boundary + "--" + "\r\n");
                ds.flush();
                ds.close();
                if(httpUrlCon.getResponseCode() == HttpURLConnection.HTTP_OK){
                    StringBuffer sb = new StringBuffer();
                    InputStream is = httpUrlCon.getInputStream();    
                    byte[] data = new byte[1024];
                    int leng = -1;
                    while((leng = is.read(data)) != -1) {
                        sb.append(new String(data, 0, leng));
                    }                
                    result = sb.toString();    
                }
            } catch (MalformedURLException e) {
                Log.e("ERROR", "ERROR: " + e.toString());
                e.printStackTrace();
            } catch (IOException e) {
                Log.e("ERROR", "ERROR: " + e.toString());
                e.printStackTrace();
            }
            return result;                        
        }
  • 12-13行目
    multipartform-data形式を指定しています。
MemoEditActivity.java (4)
        public void saveNewTitleMemo() {  
            new AsyncTask<String, Void, String>(){
                @Override
                protected String doInBackground(String... params) {  
                    String parameter = "?WRITEPROTECT=ON&UPDIR=/" + rootDir;    
                    parameter = parameter + "&FTIME=" + getDateTime16();
                    String filename = date.replaceAll("/", "") + ".txt";
                    String rtnStr = "";                    
                    rtnStr = FlashAirRequest.getString(params[0] + parameter);
                    if(rtnStr.toUpperCase(Locale.getDefault()).equals("SUCCESS")) {
                        rtnStr = FlashAirRequest
                                .upload( params[0], filename, newTitle + "\n" + newMemo);
                    }
                    return     rtnStr;
                }    
                @Override
                protected void onPostExecute(String result) {
                    if(result.toUpperCase(Locale.getDefault()).indexOf("SUCCESS") >= 0) {
                        Toast.makeText(MemoEditActivity.this, "Save Completed.", 
                                        Toast.LENGTH_LONG).show();
                    }                    
                }
            }.execute("http://flashair/upload.cgi");
        }
  • 5-9行目
    手順の4-6を実行しています。
    getDateTime16() は、現在の日付からファイルの作成日時を指定するための日付文字列を生成する関数です。
    日付、時刻をそれぞれ16ビットの16進数として作成し、合わせて32ビットの1つの値として渡しています。

  • 11-12行目
    作成したupload() を使用して、タイトル&メモをアップロードしています。

ファイル作成日時用の日付文字列の取得

前述した、現在の日付からファイルの作成日時用の日付文字列を生成する関数です。

MemoEditActivity.java (5)
        public String getDateTime16() {

            Calendar calendar = Calendar.getInstance();
            int year = (calendar.get(Calendar.YEAR) - 1980) << 9;
            int month = (calendar.get(Calendar.MONTH) + 1) << 5;
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            int hours = calendar.get(Calendar.HOUR_OF_DAY) << 11;
            int minites = calendar.get(Calendar.MINUTE) << 5;
            int seconds = calendar.get(Calendar.SECOND) / 2;
            String rtnStr = "0x" +  Integer.toHexString(year + month + day) 
                                        + Integer.toHexString(hours + minites + seconds);

            return rtnStr;
        }

ファイル名用日付文字列の取得

対象となっている日付文字列(YYYY/MM/DD)から、16ビット整数を10進数表記した日付形式の日付文字列を生成する関数です。

MemoEditActivity.java (6)
        public String getDate16(String date) {
            String rtnStr = "";        
            try{
                int year = (Integer.parseInt(date.substring(0,4)) - 1980) << 9;        
                int month = (Integer.parseInt(date.substring(5,7))) << 5;        
                int day = Integer.parseInt(date.substring(8,10));        
                rtnStr = String.valueOf(year + month + day); 
            }
            catch(Exception e) {
                Log.e("ERROR", "ERROR: " + e.toString());
                e.printStackTrace();
            }                  
             return rtnStr;
        }

実行結果

メモ&タイトルを保存してみます。

This image shows the result

This image shows the result

メモ&タイトルを入力し、Save ボタンをタップして保存します。

This image shows the result

Back ボタン をタップし前画面に戻ると、保存した内容が表示されました。

This image shows the result


サンプルコード

android_tutorial_07.zip (533KB)

このサイトのサンプルコードは二条項BSDライセンスで提供されています。