OSDN Git Service

ストレージを外部ストレージではなくて内部ストレージに変更する。
[gokigen/MeMoMa.git] / app / src / main / java / jp / sourceforge / gokigen / memoma / io / ObjectLayoutCaptureExporter.java
1 package jp.sourceforge.gokigen.memoma.io;
2
3 import static jp.sourceforge.gokigen.memoma.Main.APP_NAMESPACE;
4
5 import java.io.File;
6 import java.io.OutputStream;
7 import java.text.SimpleDateFormat;
8 import java.util.Calendar;
9 import java.util.Enumeration;
10 import java.util.Locale;
11
12 import android.app.ProgressDialog;
13 import android.content.ContentResolver;
14 import android.content.ContentValues;
15 import android.content.Context;
16 import android.database.Cursor;
17 import android.database.DatabaseUtils;
18 import android.graphics.Bitmap;
19 import android.graphics.Canvas;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.net.Uri;
23 import android.os.AsyncTask;
24 import android.os.Build;
25 import android.os.Environment;
26 import android.provider.MediaStore;
27 import android.util.Log;
28 import android.view.Display;
29
30 import androidx.appcompat.app.AppCompatActivity;
31
32 import jp.sourceforge.gokigen.memoma.R;
33 import jp.sourceforge.gokigen.memoma.drawers.MeMoMaCanvasDrawer;
34 import jp.sourceforge.gokigen.memoma.holders.MeMoMaObjectHolder;
35 import jp.sourceforge.gokigen.memoma.holders.PositionObject;
36
37 /**
38  *  データをファイルに保存するとき用 アクセスラッパ (非同期処理を実行)
39  *  Viewの情報を画像形式(png形式)で保存する。
40  *  どのViewを保存するのかは、ICaptureExporter.getCaptureTargetView()クラスを使って教えてもらう。
41  *  AsyncTask
42  *    String       : 実行時に渡すクラス(Param)           : ファイル名をもらう
43  *    Integer    : 途中経過を伝えるクラス(Progress)   : 今回は使っていない
44  *    String      : 処理結果を伝えるクラス(Result)      : 結果を応答する。
45  */
46 public class ObjectLayoutCaptureExporter extends AsyncTask<String, Integer, String>
47 {
48     private final String TAG = toString();
49     private static final boolean dumpLog = false;
50
51     private static final int OUTPUT_EXPORT_SHARE_ID = 1000;
52         private static final int OUTPUT_MARGIN = 8;
53         private static final int OUTPUT_MARGIN_TOP = 50;
54         
55         private static final int MINIMUM_WIDTH = 800;
56         private static final int MINIMUM_HEIGHT = 600;
57
58         private final ICaptureLayoutExporter receiver;
59
60         private final MeMoMaObjectHolder objectHolder;
61         private final MeMoMaCanvasDrawer canvasDrawer;
62         private final ProgressDialog savingDialog;
63         private float offsetX = 0.0f;
64         private float offsetY = 0.0f;
65         private int displayWidth;
66         private int displayHeight;
67
68     private final Context context;
69
70     private Uri exportedUri = null;
71
72         /**
73          *   コンストラクタ
74          */
75         ObjectLayoutCaptureExporter(AppCompatActivity context, MeMoMaObjectHolder holder, MeMoMaCanvasDrawer drawer, ICaptureLayoutExporter resultReceiver)
76     {
77         this.context = context;
78         receiver = resultReceiver;
79         objectHolder = holder;
80         canvasDrawer = drawer;
81
82         // 現在の画面サイズを取得
83         Display display = context.getWindowManager().getDefaultDisplay();
84         displayWidth = display.getWidth();
85         displayHeight = display.getHeight();
86
87         //  プログレスダイアログ(「保存中...」)を表示する。
88         savingDialog = new ProgressDialog(context);
89         savingDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
90         savingDialog.setMessage(context.getString(R.string.dataSaving));
91         savingDialog.setIndeterminate(true);
92         savingDialog.setCancelable(false);
93         savingDialog.show();
94
95         // ファイルをバックアップするディレクトリを作成する
96         File dir = new File(context.getFilesDir() + "/exported");
97         if (!dir.mkdir())
98         {
99             Log.v(TAG, "mkdir is failed.");
100         }
101     }
102         
103     /**
104      *  非同期処理実施前の前処理
105      */
106     @Override
107     protected void onPreExecute()
108     {
109         // なにもしない。
110     }
111
112     /**
113      *    ビットマップデータを(PNG形式で)保管する。
114      */
115     private String exportToFile(String baseName, Bitmap targetImage)
116     {
117         String resultMessage = "";
118         try
119         {
120             String outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath() + "/" + APP_NAMESPACE + "/";
121             ContentResolver resolver = context.getContentResolver();
122             String fileName = baseName + ".png";
123
124             Uri extStorageUri;
125             ContentValues values = new ContentValues();
126             values.put(MediaStore.Images.Media.TITLE, fileName);
127             values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
128             values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
129             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
130             {
131                 values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + APP_NAMESPACE);
132                 values.put(MediaStore.Images.Media.IS_PENDING, true);
133                 extStorageUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
134             }
135             else
136             {
137                 File path = new File(outputDir);
138                 values.put(MediaStore.Images.Media.DATA, path.getAbsolutePath() + File.separator + fileName);
139                 extStorageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
140             }
141             Log.v(TAG, "---------- " + baseName + ".png " + values);
142             Uri imageUri = resolver.insert(extStorageUri, values);
143             if (imageUri != null)
144             {
145                 ////////////////////////////////////////////////////////////////
146                 if (dumpLog)
147                 {
148                     try
149                     {
150                         Cursor cursor = resolver.query(imageUri, null, null, null, null);
151                         DatabaseUtils.dumpCursor(cursor);
152                         cursor.close();
153                     }
154                     catch (Exception e)
155                     {
156                         e.printStackTrace();
157                         resultMessage = e.getMessage();
158                     }
159                 }
160                 ////////////////////////////////////////////////////////////////
161
162                 OutputStream outputStream = resolver.openOutputStream(imageUri, "wa");
163                 if (outputStream != null)
164                 {
165                     targetImage.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
166                     outputStream.flush();
167                     outputStream.close();
168                 }
169                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
170                 {
171                     values.put(MediaStore.Images.Media.IS_PENDING, false);
172                     resolver.update(imageUri, values, null, null);
173                 }
174             }
175             else
176             {
177                 Log.v(TAG, " cannot get imageUri...");
178             }
179             exportedUri = imageUri;
180         }
181         catch (Throwable t)
182         {
183             t.printStackTrace();
184             resultMessage = t.getMessage();
185             exportedUri = null;
186         }
187         return (resultMessage);
188     }
189
190     /**
191      *    キャンバスの大きさがどれくらい必要か、チェックする。
192      */
193     private Rect checkCanvasSize()
194     {
195         Rect canvasSize = new Rect();
196
197         // オブジェクトの配置位置を探る。
198         Enumeration<Integer> keys = objectHolder.getObjectKeys();
199         while (keys.hasMoreElements())
200         {
201             Integer key = keys.nextElement();
202             PositionObject pos = objectHolder.getPosition(key);
203             RectF posRect = pos.getRect();
204             if (canvasSize.left > posRect.left)
205             {
206                 canvasSize.left = (int) posRect.left;
207             }
208             if (canvasSize.right < posRect.right)
209             {
210                 canvasSize.right = (int) posRect.right;
211             }
212             if (canvasSize.top > posRect.top)
213             {
214                 canvasSize.top = (int) posRect.top;
215             }
216             if (canvasSize.bottom < posRect.bottom)
217             {
218                 canvasSize.bottom = (int) posRect.bottom;
219             }
220         }
221         
222         // 描画領域にちょっと余裕を持たせる
223         canvasSize.left = canvasSize.left - OUTPUT_MARGIN;
224         canvasSize.right = canvasSize.right + OUTPUT_MARGIN;
225         canvasSize.top = canvasSize.top - OUTPUT_MARGIN_TOP;
226         canvasSize.bottom = canvasSize.bottom + OUTPUT_MARGIN;
227         canvasSize.sort();
228
229         // 現在の画面サイズを取得
230         if (displayWidth < MINIMUM_WIDTH)
231         {
232             displayWidth = MINIMUM_WIDTH;
233         }
234         if (displayHeight < MINIMUM_HEIGHT)
235         {
236             displayHeight = MINIMUM_HEIGHT;
237         }        
238
239         // 出力の最小サイズを(表示画面サイズに)設定
240         if (canvasSize.width() < displayWidth)
241         {
242                 canvasSize.right = canvasSize.left + displayWidth;
243         }
244         if (canvasSize.height() < displayHeight)
245         {
246                 canvasSize.bottom = canvasSize.top + displayHeight;
247         }
248
249         // 画像位置(キャンバス位置)の調整。。。
250         offsetX = 0.0f - canvasSize.left - (OUTPUT_MARGIN);
251         offsetY = 0.0f - canvasSize.top - (OUTPUT_MARGIN);
252
253         // 出力する画像データのサイズを表示する
254         Log.v(TAG, "ObjectLayoutCaptureExporter::checkCanvasSize() w:" + canvasSize.width() + " , h:" + canvasSize.height() + "  offset :(" + offsetX + "," + offsetY + ")");
255         return (canvasSize);
256     }    
257
258     /**
259      *  非同期処理
260      *  (バックグラウンドで実行する(このメソッドは、UIスレッドと別のところで実行する))
261      */
262     @Override
263     protected String doInBackground(String... datas)
264     {
265         String result = "";
266         try
267         {
268             Rect canvasSize = checkCanvasSize();
269             Bitmap targetBitmap = Bitmap.createBitmap(canvasSize.width(), canvasSize.height(), Bitmap.Config.RGB_565);
270             Canvas targetCanvas = new Canvas(targetBitmap);
271
272             // オブジェクトをビットマップの中に書き込む
273             canvasDrawer.drawOnBitmapCanvas(targetCanvas, offsetX, offsetY);
274
275             String fileName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Calendar.getInstance().getTime()) + "_" + datas[0];
276
277             // データを保管する
278             result = exportToFile(fileName, targetBitmap);
279         }
280         catch (Throwable t)
281         {
282             t.printStackTrace();
283         }
284         System.gc();
285         return (result);
286     }
287
288     /**
289      *  非同期処理の進捗状況の更新
290      * 
291      */
292         @Override
293         protected void onProgressUpdate(Integer... values)
294         {
295         // 今回は何もしない
296         }
297
298     /**
299      *  非同期処理の後処理
300      *  (結果を応答する)
301      */
302     @Override
303     protected void onPostExecute(String result)
304     {
305         try
306         {
307             if (receiver != null)
308             {
309                 receiver.onCaptureLayoutExportedResult(exportedUri, result, OUTPUT_EXPORT_SHARE_ID);
310             }
311         }
312         catch (Exception ex)
313         {
314                 Log.v(TAG, "ViewCaptureExporter::onPostExecute() : " + ex.getMessage());
315             ex.printStackTrace();
316         }
317         // プログレスダイアログを消す
318         if (savingDialog != null)
319         {
320             savingDialog.dismiss();
321         }
322     }     
323  
324     /**
325      *    結果報告用のインタフェース
326      */
327     public interface ICaptureLayoutExporter
328     {
329         //  保存結果の報告
330         void onCaptureLayoutExportedResult(Uri exportedUri, String detail, int id);
331     }
332 }