Rabu, 25 September 2019

Unboxing dan Review Speaker Robot RS170

Suatu hari penulis melihat-lihat speaker di Shopee dan penulis tertarik dengan speaker merek ROBOT tipe RS170. Harga murah, bentuk keren karena ada subwoofer-nya dengan harga termurah di Rp. 63.xxx,- disalah satu lapak Shopee. Penulis mencoba mencari review tentang speaker ini, tetapi penulis tidak menemukan sama sekali. Penulis hanya membaca review dari pembeli di Shopee maupun di Lazada yang mengatakan bahwa speaker ini layak dibeli.

Wel, akhirnya penulis pun tertarik membelinya karena harga juga murah, dan waktu itu ada pengembalian dana sebesar Rp. 63.xxx,- juga dari salah satu seller di Shopee karena stok barang tidak ada. Lumayan lah, tidak ada ruginya juga membeli speaker tersebut, itung-itung kalau suara jelek yaa memang dengan harga segitu masa mau dapat kualitas yang waah untuk sebuah speaker. Penulis pun membeli speaker tersebut beserta dengan tinta printer Canon dilapak yang sama.

Akhirnya, speaker pun tiba tanggal 25 September 2019 kemarin, langsung saja penulis unboxing sekalian me-review speaker ini.

Tampilan Box Speaker Robot RS170 Masih Tersegel
Speaker yang penulis terima masih tersegel rapi, berarti dari seller tidak dilakukan pengecekan sama sekali. Mungkin seller tidak ingin mengecewakan konsumen dengan tidak membuka segel (mungkin takut dibilang barang bekas hehehehe...).

Didalam Kotak Speaker Berisi Speaker, Kartu Petunjuk dan Kartu Garansi
Penulis langsung unboxing speaker ini, didalam kotak berisi speaker set yang dibungkus dengan bubble wrap, kartu petunjuk penggunaan serta kartu garansi. Di kartu garansi ini tertera tanggal produksi speaker ini, yaitu tertanggal 10 Mei 2019.

Tampilan Speaker Setelah Bubble Wrap Dibuka
Kemudian penulis langsung membuka bubble wrap dan inilah tampilan speaker aslinya. Bentuknya termasuk keren bagi penulis, mengingatkan bentuk speaker Sony warna maroon milik penulis yang dibeli sekitar 7 tahun yang lalu. Speaker ini bobotnya juga termasuk ringan lhoo, awalnya penulis tidak yakin dengan berat yang ringan ini, biasanya kalo bobot ringan itu berarti "kualitas" elektroniknya murahan!

Pengujian Speaker Dilaptop Menggunakan Lagu "Fly Away" Lenny Kravitz
Kemudian penulis langsung membawa speaker ini kedalam kamar untuk dilakukan pengujian suara menggunakan laptop, tancapkan kabel power ke USB port serta tancapkan jack 3.5mm ke output jack dilaptop. Langsung saja penulis memutar lagu "Fly Away"-nya Lenny Kravitz dan volume suara speaker dan laptop penulis putar dari terkecil hingga mentok. Selain itu, penulis juga menguji menggunakan lagu "Kamu Hoaxxx"-nya Boiyen dan "Zumbao"-nya Taboo.

Hasilnya? Bobot ringan itu berarti "kualitas" elektroniknya murahan itu tidak berlaku di speaker ini. Suara yang dihasilkan oleh speaker ini termasuk "Great" menurut penulis, karena dengan harga yang cuma Rp. 63.xxx,- suara yang dihasilkan termasuk mantabbb, terutama untuk bass tidak akan pecah meskipun volume dilaptop maupun dispeaker itu di mentokkan. Kerennya juga ada cahaya berwarna ungu di subwoofer-nya sehingga ada hiburan untuk melihat gerakan subwoofer ketika ada nada bass. Kekurangannya cuman satu, yaitu treble agak dominan daripada bass-nya sehingga kalau volume dibesarkan, maka treble lebih dominan tetapi tidak menutup keseluruhan dari suara bass-nya.

Cukup sekian review dari penulis tentang speaker Robot RS170 ini, semoga bermanfaat bagi anda yang punya budget minim tapi kepingin speaker maksi hehehe...

Kamis, 12 September 2019

Class FileUtils Untuk Mendapatkan File Real Path Pada Pemrograman Android

Ketika membuat aplikasi pada OS Android, kadang kala kita membutuhkan operasi baca dan tulis suatu berkas (file) di Androd. Penulis pernah menghadapi masalah ini, ketika mengambil suatu berkas, eh ternyata Android memberikan alamat path (alamat yang langsung menunjukkan letaknya) berkas tersebut dalam bentuk URI, bukan path langsung.

Setelah berselancar diinternet, akhirnya penulis mendapatkan fungsi FileUtils yang di-posting oleh Satyawan Hajare di alamat https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android. Di forum Stack Overflow ini dijelaskan dengan detail cara penggunaan serta kode sumbernya. Tetapi tidak ada salahnya penulis juga reposting kode sumber tersebut disini, berikut ini kode sumber FileUtils.java :

1:  import android.annotation.SuppressLint;  
2:  import android.content.ContentUris;  
3:  import android.content.Context;  
4:  import android.database.Cursor;  
5:  import android.net.Uri;  
6:  import android.os.Build;  
7:  import android.os.Environment;  
8:  import android.provider.DocumentsContract;  
9:  import android.provider.MediaStore;  
10:  import android.provider.OpenableColumns;  
11:  import android.text.TextUtils;  
12:  import android.util.Log;  
13:  import java.io.File;  
14:  import java.io.FileOutputStream;  
15:  import java.io.InputStream;  
16:  import java.util.List;  
17:  public class FileUtils {  
18:    private static Uri contentUri = null;  
19:    /**  
20:     * Get a file path from a Uri. This will get the the path for Storage Access  
21:     * Framework Documents, as well as the _data field for the MediaStore and  
22:     * other file-based ContentProviders.<br>  
23:     * <br>  
24:     * Callers should check whether the path is local before assuming it  
25:     * represents a local file.  
26:     *  
27:     * @param context The context.  
28:     * @param uri   The Uri to query.  
29:     */  
30:    @SuppressLint("NewApi")  
31:    public static String getPath(final Context context, final Uri uri) {  
32:      // check here to KITKAT or new version  
33:      final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;  
34:      String selection = null;  
35:      String[] selectionArgs = null;  
36:      // DocumentProvider  
37:      if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {  
38:        // ExternalStorageProvider  
39:        if (isExternalStorageDocument(uri)) {  
40:          final String docId = DocumentsContract.getDocumentId(uri);  
41:          final String[] split = docId.split(":");  
42:          final String type = split[0];  
43:          String fullPath = getPathFromExtSD(split);  
44:          if (fullPath != "") {  
45:            return fullPath;  
46:          } else {  
47:            return null;  
48:          }  
49:        }  
50:        // DownloadsProvider  
51:        else if (isDownloadsDocument(uri)) {  
52:          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {  
53:            final String id;  
54:            Cursor cursor = null;  
55:            try {  
56:              cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);  
57:              if (cursor != null && cursor.moveToFirst()) {  
58:                String fileName = cursor.getString(0);  
59:                String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;  
60:                if (!TextUtils.isEmpty(path)) {  
61:                  return path;  
62:                }  
63:              }  
64:            } finally {  
65:              if (cursor != null)  
66:                cursor.close();  
67:            }  
68:            id = DocumentsContract.getDocumentId(uri);  
69:            if (!TextUtils.isEmpty(id)) {  
70:              if (id.startsWith("raw:")) {  
71:                return id.replaceFirst("raw:", "");  
72:              }  
73:              String[] contentUriPrefixesToTry = new String[]{  
74:                  "content://downloads/public_downloads",  
75:                  "content://downloads/my_downloads"  
76:              };  
77:              for (String contentUriPrefix : contentUriPrefixesToTry) {  
78:                try {  
79:                  final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));  
80:                  return getDataColumn(context, contentUri, null, null);  
81:                } catch (NumberFormatException e) {  
82:                  //In Android 8 and Android P the id is not a number  
83:                  return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", "");  
84:                }  
85:              }  
86:            }  
87:          } else {  
88:            final String id = DocumentsContract.getDocumentId(uri);  
89:            //final boolean isOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;  
90:            if (id.startsWith("raw:")) {  
91:              return id.replaceFirst("raw:", "");  
92:            }  
93:            try {  
94:              contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));  
95:            } catch (NumberFormatException e) {  
96:              e.printStackTrace();  
97:            }  
98:            if (contentUri != null) {  
99:              return getDataColumn(context, contentUri, null, null);  
100:            }  
101:          }  
102:        }  
103:        // MediaProvider  
104:        else if (isMediaDocument(uri)) {  
105:          final String docId = DocumentsContract.getDocumentId(uri);  
106:          final String[] split = docId.split(":");  
107:          final String type = split[0];  
108:          Uri contentUri = null;  
109:          if ("image".equals(type)) {  
110:            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;  
111:          } else if ("video".equals(type)) {  
112:            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;  
113:          } else if ("audio".equals(type)) {  
114:            contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;  
115:          }  
116:          selection = "_id=?";  
117:          selectionArgs = new String[]{split[1]};  
118:          return getDataColumn(context, contentUri, selection, selectionArgs);  
119:        } else if (isGoogleDriveUri(uri)) {  
120:          return getDriveFilePath(uri, context);  
121:        }  
122:      }  
123:      // MediaStore (and general)  
124:      else if ("content".equalsIgnoreCase(uri.getScheme())) {  
125:        if (isGooglePhotosUri(uri)) {  
126:          return uri.getLastPathSegment();  
127:        }  
128:        if (isGoogleDriveUri(uri)) {  
129:          return getDriveFilePath(uri, context);  
130:        }  
131:        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N) {  
132:          // return getFilePathFromURI(context,uri);  
133:          return getMediaFilePathForN(uri, context);  
134:          // return getRealPathFromURI(context,uri);  
135:        } else {  
136:          return getDataColumn(context, uri, null, null);  
137:        }  
138:      }  
139:      // File  
140:      else if ("file".equalsIgnoreCase(uri.getScheme())) {  
141:        return uri.getPath();  
142:      }  
143:      return null;  
144:    }  
145:    /**  
146:     * Check if a file exists on device  
147:     *  
148:     * @param filePath The absolute file path  
149:     */  
150:    private static boolean fileExists(String filePath) {  
151:      File file = new File(filePath);  
152:      return file.exists();  
153:    }  
154:    /**  
155:     * Get full file path from external storage  
156:     *  
157:     * @param pathData The storage type and the relative path  
158:     */  
159:    private static String getPathFromExtSD(String[] pathData) {  
160:      final String type = pathData[0];  
161:      final String relativePath = "/" + pathData[1];  
162:      String fullPath = "";  
163:      // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string  
164:      // something like "71F8-2C0A", some kind of unique id per storage  
165:      // don't know any API that can get the root path of that storage based on its id.  
166:      //  
167:      // so no "primary" type, but let the check here for other devices  
168:      if ("primary".equalsIgnoreCase(type)) {  
169:        fullPath = Environment.getExternalStorageDirectory() + relativePath;  
170:        if (fileExists(fullPath)) {  
171:          return fullPath;  
172:        }  
173:      }  
174:      // Environment.isExternalStorageRemovable() is `true` for external and internal storage  
175:      // so we cannot relay on it.  
176:      //  
177:      // instead, for each possible path, check if file exists  
178:      // we'll start with secondary storage as this could be our (physically) removable sd card  
179:      fullPath = System.getenv("SECONDARY_STORAGE") + relativePath;  
180:      if (fileExists(fullPath)) {  
181:        return fullPath;  
182:      }  
183:      fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath;  
184:      if (fileExists(fullPath)) {  
185:        return fullPath;  
186:      }  
187:      return fullPath;  
188:    }  
189:    private static String getDriveFilePath(Uri uri, Context context) {  
190:      Uri returnUri = uri;  
191:      Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);  
192:      /*  
193:       * Get the column indexes of the data in the Cursor,  
194:       *   * move to the first row in the Cursor, get the data,  
195:       *   * and display it.  
196:       * */  
197:      int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);  
198:      int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);  
199:      returnCursor.moveToFirst();  
200:      String name = (returnCursor.getString(nameIndex));  
201:      String size = (Long.toString(returnCursor.getLong(sizeIndex)));  
202:      File file = new File(context.getCacheDir(), name);  
203:      try {  
204:        InputStream inputStream = context.getContentResolver().openInputStream(uri);  
205:        FileOutputStream outputStream = new FileOutputStream(file);  
206:        int read = 0;  
207:        int maxBufferSize = 1 * 1024 * 1024;  
208:        int bytesAvailable = inputStream.available();  
209:        //int bufferSize = 1024;  
210:        int bufferSize = Math.min(bytesAvailable, maxBufferSize);  
211:        final byte[] buffers = new byte[bufferSize];  
212:        while ((read = inputStream.read(buffers)) != -1) {  
213:          outputStream.write(buffers, 0, read);  
214:        }  
215:        Log.e("File Size", "Size " + file.length());  
216:        inputStream.close();  
217:        outputStream.close();  
218:        Log.e("File Path", "Path " + file.getPath());  
219:        Log.e("File Size", "Size " + file.length());  
220:      } catch (Exception e) {  
221:        Log.e("Exception", e.getMessage());  
222:      }  
223:      return file.getPath();  
224:    }  
225:    private static String getMediaFilePathForN(Uri uri, Context context) {  
226:      Uri returnUri = uri;  
227:      Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);  
228:      /*  
229:       * Get the column indexes of the data in the Cursor,  
230:       *   * move to the first row in the Cursor, get the data,  
231:       *   * and display it.  
232:       * */  
233:      int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);  
234:      int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);  
235:      returnCursor.moveToFirst();  
236:      String name = (returnCursor.getString(nameIndex));  
237:      String size = (Long.toString(returnCursor.getLong(sizeIndex)));  
238:      File file = new File(context.getFilesDir(), name);  
239:      try {  
240:        InputStream inputStream = context.getContentResolver().openInputStream(uri);  
241:        FileOutputStream outputStream = new FileOutputStream(file);  
242:        int read = 0;  
243:        int maxBufferSize = 1 * 1024 * 1024;  
244:        int bytesAvailable = inputStream.available();  
245:        //int bufferSize = 1024;  
246:        int bufferSize = Math.min(bytesAvailable, maxBufferSize);  
247:        final byte[] buffers = new byte[bufferSize];  
248:        while ((read = inputStream.read(buffers)) != -1) {  
249:          outputStream.write(buffers, 0, read);  
250:        }  
251:        Log.e("File Size", "Size " + file.length());  
252:        inputStream.close();  
253:        outputStream.close();  
254:        Log.e("File Path", "Path " + file.getPath());  
255:        Log.e("File Size", "Size " + file.length());  
256:      } catch (Exception e) {  
257:        Log.e("Exception", e.getMessage());  
258:      }  
259:      return file.getPath();  
260:    }  
261:    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {  
262:      Cursor cursor = null;  
263:      final String column = "_data";  
264:      final String[] projection = {column};  
265:      try {  
266:        cursor = context.getContentResolver().query(uri, projection,  
267:            selection, selectionArgs, null);  
268:        if (cursor != null && cursor.moveToFirst()) {  
269:          final int index = cursor.getColumnIndexOrThrow(column);  
270:          return cursor.getString(index);  
271:        }  
272:      } finally {  
273:        if (cursor != null)  
274:          cursor.close();  
275:      }  
276:      return null;  
277:    }  
278:    /**  
279:     * @param uri - The Uri to check.  
280:     * @return - Whether the Uri authority is ExternalStorageProvider.  
281:     */  
282:    private static boolean isExternalStorageDocument(Uri uri) {  
283:      return "com.android.externalstorage.documents".equals(uri.getAuthority());  
284:    }  
285:    /**  
286:     * @param uri - The Uri to check.  
287:     * @return - Whether the Uri authority is DownloadsProvider.  
288:     */  
289:    private static boolean isDownloadsDocument(Uri uri) {  
290:      return "com.android.providers.downloads.documents".equals(uri.getAuthority());  
291:    }  
292:    /**  
293:     * @param uri - The Uri to check.  
294:     * @return - Whether the Uri authority is MediaProvider.  
295:     */  
296:    private static boolean isMediaDocument(Uri uri) {  
297:      return "com.android.providers.media.documents".equals(uri.getAuthority());  
298:    }  
299:    /**  
300:     * @param uri - The Uri to check.  
301:     * @return - Whether the Uri authority is Google Photos.  
302:     */  
303:    private static boolean isGooglePhotosUri(Uri uri) {  
304:      return "com.google.android.apps.photos.content".equals(uri.getAuthority());  
305:    }  
306:    /**  
307:     * @param uri The Uri to check.  
308:     * @return Whether the Uri authority is Google Drive.  
309:     */  
310:    private static boolean isGoogleDriveUri(Uri uri) {  
311:      return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority());  
312:    }  
313:  }  

Cara penggunaan class FileUtils sebagai berikut:

 /* Untuk menyimpan lokasi file yang dipilih */  
 Uri pilihFile=null;  
 /* Untuk menyimpan nama file dan path nya */  
 String pathFile;  
   
 // Ambil Uri file  
 pilihFile = data.getData();  
 // Cek, apakah isi pilihFile berisi Uri?  
 if (pilihFile != null) {  
   // Jika keduanya YA , kerjakan perintah berikut ini:  
   
   /* Panggil class FileUtils untuk mendapatkan path dari berkas yang akan digunakan dan  
      simpan hasilnya di variabel pathFile */  
   pathFile = FileUtils.getPath(getActivity(),pilihFile);  
 }  

Selamat menggunakan class FileUtil ini diprogram Android buatan anda sendiri...

Jumat, 06 September 2019

Pemrograman Aplikasi Tab Swipeable View Seperti Aplikasi WhatsApp

Pada saat penulis membuat tesis S2, yaitu membuat suatu program steganografi, penulis sangat tertarik dengan model aplikasi WhatsApp, dimana aplikasi ini menggunakan beberapa Tabs untuk memisahkan fungsinya. Serta pengguna dapat melakukan swipe (menggeser pada layar) untuk berpindah antar halaman pada Tabs tersebut.

Penulis berburu beberapa kode pada internet, banyak yang beredar cara-cara membuat aplikasi yang "mirip" dengan WhatsApp, ada beberapa kode yang berjalan dengan baik di emulator Android SDK tetapi tidak dapat berjalan di emulator maupun ponsel Android. Ada beberapa kode yang mencontohkan, tetapi tidak menjelaskan bagaimana cara menambahkan suatu TextEdit, Button, dll pada halaman pada Tabs tersebut, hanya tampilanTabs saja. Akhirnya penulis menemukan tutorial kode "Tab Layout with Swipeable View Using Fragments in Android App" di alamat blog http://androidminess.blogspot.com/2016/01/android-tab-layout-with-swipeable-view.html. Penulis mengikuti cara yang dijelaskan pada blog tersebut, tetapi ketika di Generate APK di Android SDK ternyata banyak terjadi kesalahan. Akhirnya penulis menelusuri secara perlahan-lahan untuk mengetahui cara kerja dari kode program tersebut dan akhirnya penulis berhasil mengkoreksi kode di MainActivity.java serta strings.xml, karena di kedua file tersebut terjadi kesalahan dalam memanggil reference string.

Tampilan Tab Swipeable View Yang Diperbaiki dan Dirubah Penulis

Tampilan Menu Pada Tab Swipeable View

Usut punya usut, ternyata cara kerja Tabs pada aplikasi WhatApps adalah menggunakan Fragment, yaitu "memecah" layar "utama" menjadi beberapa halaman, sehingga halaman-halaman tersebut bisa ditampilkan pada layar "utama" bila Tabs tersebut dipilih.

Untuk mengubah jumlah fragment dapat dilakukan di berkas SectionPagerAdapter.java di baris kode berikut:

  @Override  
   public int getCount() {  
     // Show 3 total pages.  
     return 3; <- ganti dengan jumlah fragment yang diinginkan  
   }  
   
 untuk mengganti nama Tabs dilakukan pada baris kode berikut:  
   
 @Override  
   public CharSequence getPageTitle(int position) {  
     String tabName="";  
     switch (position) {  
       // Nama-nama Tiap TABS disini  
       case 0:  
         tabName = mContext.getResources().getString(R.string.app_tab1);  
         return tabName; <- ganti dengan nama Tabs yang diinginkan  
         // return "STEGO";  
       case 1:  
         tabName = mContext.getResources().getString(R.string.app_tab2);  
         return tabName; <- ganti dengan nama Tabs yang diinginkan  
         //return "CHAT";  
       case 2:  
         tabName = mContext.getResources().getString(R.string.app_tab3);  
         return tabName; <- ganti dengan nama Tabs yang diinginkan  
         //return "SETTINGS";  
     }  
     return null;  
   }  
   
 Untuk menampilkan isi halaman pada fragment dapat dilakukan di berkas MainActivity.java pada baris kode berikut:  
   
 switch (getArguments().getInt(ARG_SECTION_NUMBER)) {  
         case 1:  
           viewFrag = inflater.inflate(R.layout.frag_stego, container, false); <- ganti dengan nama berkas fragment .xml yang akan ditampilkan  
           textView = (TextView) viewFrag.findViewById(R.id.stego_label);  
           textView.setText(getString(R.string.app_tab1)+" "+  
           getArguments().getInt(ARG_SECTION_NUMBER));  
           return viewFrag;  
         case 2:  
           viewFrag = inflater.inflate(R.layout.frag_chat, container, false); <- ganti dengan nama berkas fragment .xml yang akan ditampilkan  
           textView = (TextView) viewFrag.findViewById(R.id.chat_label);  
           textView.setText(getString(R.string.app_tab2)+" "+  
           getArguments().getInt(ARG_SECTION_NUMBER));  
           return viewFrag;  
         case 3:  
           viewFrag = inflater.inflate(R.layout.frag_settings, container, false); <- ganti dengan nama berkas fragment .xml yang akan ditampilkan  
           textView = (TextView) viewFrag.findViewById(R.id.settings_label);  
           textView.setText(getString(R.string.app_tab3)+" "+  
           getArguments().getInt(ARG_SECTION_NUMBER));  
           return viewFrag;  
       }  
       return null;  

Kode sumber program ini dapat diunduh disini. Silahkan dicoba dan galilah kreasi anda dalam mengembangkan aplikasi Android.

Happy programming!!!