Jelajahi Sumber

first commit

xuxinyi 1 tahun lalu
melakukan
e8fe3dae4e
67 mengubah file dengan 3099 tambahan dan 0 penghapusan
  1. 40 0
      .gitignore
  2. 22 0
      LICENSE
  3. 37 0
      README.md
  4. TEMPAT SAMPAH
      Screenshot/app_icon.png
  5. TEMPAT SAMPAH
      Screenshot/demo.jpg
  6. 1 0
      amraudiorecorder/.gitignore
  7. 24 0
      amraudiorecorder/build.gradle
  8. 17 0
      amraudiorecorder/proguard-rules.pro
  9. 11 0
      amraudiorecorder/src/main/AndroidManifest.xml
  10. 197 0
      amraudiorecorder/src/main/java/com/water/amraudiorecorder/AMRAudioRecorder.java
  11. 3 0
      amraudiorecorder/src/main/res/values/strings.xml
  12. 1 0
      app/.gitignore
  13. 27 0
      app/build.gradle
  14. 17 0
      app/proguard-rules.pro
  15. 27 0
      app/src/main/AndroidManifest.xml
  16. 51 0
      app/src/main/java/com/water/example/EasyTimer.java
  17. 157 0
      app/src/main/java/com/water/example/MainActivity.java
  18. 63 0
      app/src/main/java/com/water/example/PlaybackActivity.java
  19. 74 0
      app/src/main/java/com/water/example/utils/BounceInterpolator.java
  20. 208 0
      app/src/main/java/com/water/example/utils/CustomButton.java
  21. 38 0
      app/src/main/java/com/water/example/utils/PermissionUtils.java
  22. 1169 0
      app/src/main/java/com/water/example/utils/PermissionsDialogue.java
  23. 36 0
      app/src/main/java/com/water/example/utils/Units.java
  24. 11 0
      app/src/main/res/anim/bounce.xml
  25. 17 0
      app/src/main/res/anim/fade_in_center.xml
  26. 17 0
      app/src/main/res/anim/fade_out_center.xml
  27. 5 0
      app/src/main/res/drawable/bg_translucent_white.xml
  28. 12 0
      app/src/main/res/drawable/ic_calendar.xml
  29. 16 0
      app/src/main/res/drawable/ic_camera.xml
  30. 14 0
      app/src/main/res/drawable/ic_contacts.xml
  31. TEMPAT SAMPAH
      app/src/main/res/drawable/ic_delete.png
  32. TEMPAT SAMPAH
      app/src/main/res/drawable/ic_done.png
  33. 12 0
      app/src/main/res/drawable/ic_folder.xml
  34. 12 0
      app/src/main/res/drawable/ic_location.xml
  35. 13 0
      app/src/main/res/drawable/ic_mic.xml
  36. TEMPAT SAMPAH
      app/src/main/res/drawable/ic_pause.png
  37. 14 0
      app/src/main/res/drawable/ic_phone.xml
  38. TEMPAT SAMPAH
      app/src/main/res/drawable/ic_play.png
  39. 12 0
      app/src/main/res/drawable/ic_text.xml
  40. 9 0
      app/src/main/res/drawable/icon_add.xml
  41. 9 0
      app/src/main/res/drawable/icon_add_activated.xml
  42. 9 0
      app/src/main/res/drawable/icon_add_error.xml
  43. 9 0
      app/src/main/res/drawable/icon_add_pressed.xml
  44. 6 0
      app/src/main/res/drawable/icon_add_selector.xml
  45. 82 0
      app/src/main/res/layout/activity_main.xml
  46. 17 0
      app/src/main/res/layout/activity_playback.xml
  47. 112 0
      app/src/main/res/layout/alert_permissions.xml
  48. 66 0
      app/src/main/res/layout/item_permission.xml
  49. TEMPAT SAMPAH
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  50. TEMPAT SAMPAH
      app/src/main/res/mipmap-ldpi/ic_launcher.png
  51. TEMPAT SAMPAH
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  52. TEMPAT SAMPAH
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  53. TEMPAT SAMPAH
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  54. TEMPAT SAMPAH
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  55. 13 0
      app/src/main/res/values-w820dp/attrs.xml
  56. 6 0
      app/src/main/res/values-w820dp/dimens.xml
  57. 33 0
      app/src/main/res/values/colors.xml
  58. 23 0
      app/src/main/res/values/dimens.xml
  59. 3 0
      app/src/main/res/values/strings.xml
  60. 25 0
      app/src/main/res/values/styles.xml
  61. 25 0
      build.gradle
  62. 20 0
      gradle.properties
  63. TEMPAT SAMPAH
      gradle/wrapper/gradle-wrapper.jar
  64. 6 0
      gradle/wrapper/gradle-wrapper.properties
  65. 160 0
      gradlew
  66. 90 0
      gradlew.bat
  67. 1 0
      settings.gradle

+ 40 - 0
.gitignore

@@ -0,0 +1,40 @@
+# Built application files
+build/*
+*/app.iml
+
+# Crashlytics configuations
+com_crashlytics_export_strings.xml
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Gradle generated files
+.gradle/
+
+# Signing files
+.signing/
+
+# User-specific configurations
+.idea/*
+.idea/libraries/
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/.name
+.idea/compiler.xml
+.idea/copyright/profiles_settings.xml
+.idea/encodings.xml
+.idea/misc.xml
+.idea/modules.xml
+.idea/scopes/scope_settings.xml
+.idea/vcs.xml
+*.iml
+captures/*
+
+# OS-specific files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db

+ 22 - 0
LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Water
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+# AMRAudioRecorder
+![Screenshot](Screenshot/app_icon.png)
+
+Android does not support pause and resume when recording amr audio, so we should do a little trick to support pause and resume funciton.
+## Screenshot
+![Screenshot](Screenshot/demo.jpg)
+## Features
+* You can pause recording and resume it
+
+## Usage
+In Android Studio, just import module `amraudiorecorder`. In other IDE, you should copy `AMRAudioRecorder.java` into your project.
+
+~~~java
+// Note: this is not the audio file name, it's a directory.
+// AMRAudioRecorder will store audio files into this directory.
+// And this should be exist,
+// AMRAudioRecorder will not make dir if the dir does not exist.
+String recordingDirectory = "A directory absolute path";
+AMRAudioRecorder  mRecorder = new AMRAudioRecorder(recordingDirectory);
+mRecorder.start();
+~~~
+## Pause recording
+~~~java
+mRecorder.pause();
+~~~
+## Resume recording
+~~~java
+mRecorder.resume();
+~~~
+## Stop recording
+~~~java
+mRecorder.stop();
+~~~
+## Get recording file path
+~~~java
+mRecorder.getAudioFilePath();
+~~~

TEMPAT SAMPAH
Screenshot/app_icon.png


TEMPAT SAMPAH
Screenshot/demo.jpg


+ 1 - 0
amraudiorecorder/.gitignore

@@ -0,0 +1 @@
+/build

+ 24 - 0
amraudiorecorder/build.gradle

@@ -0,0 +1,24 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    testImplementation 'junit:junit:4.12'
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+}

+ 17 - 0
amraudiorecorder/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/WaterZhang/Documents/MacAndroid/android-sdk-macosx/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 11 - 0
amraudiorecorder/src/main/AndroidManifest.xml

@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.water.amraudiorecorder">
+
+    <application
+        android:allowBackup="true"
+        android:label="@string/app_name"
+        android:supportsRtl="true">
+
+    </application>
+
+</manifest>

+ 197 - 0
amraudiorecorder/src/main/java/com/water/amraudiorecorder/AMRAudioRecorder.java

@@ -0,0 +1,197 @@
+package com.water.amraudiorecorder;
+
+import android.media.MediaRecorder;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+
+/**
+ *
+ * Android does not support pause and resume when recording amr audio,
+ * so we implement it to provide pause and resume funciton.
+ *
+ * Created by Water Zhang on 11/25/15.
+ */
+public class AMRAudioRecorder  {
+
+    private boolean singleFile = true;
+
+    private MediaRecorder recorder;
+
+    private ArrayList<String> files = new ArrayList<String>();
+
+    private String fileDirectory;
+
+    private String finalAudioPath;
+
+    private boolean isRecording;
+
+    public boolean isRecording() {
+        return isRecording;
+    }
+
+    public String getAudioFilePath() {
+        return finalAudioPath;
+    }
+
+    public AMRAudioRecorder (String audioFileDirectory) {
+        this.fileDirectory = audioFileDirectory;
+
+        if (!this.fileDirectory.endsWith("/")) {
+            this.fileDirectory += "/";
+        }
+
+        newRecorder();
+    }
+
+    public boolean start() {
+        prepareRecorder();
+
+        try {
+            recorder.prepare();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+
+        if (recorder != null)
+        {
+            recorder.start();
+            isRecording = true;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean pause() {
+        if (recorder == null || !isRecording) {
+            throw new IllegalStateException("[AMRAudioRecorder] recorder is not recording!");
+        }
+
+        recorder.stop();
+        recorder.release();
+        recorder = null;
+
+        isRecording = false;
+
+        return true;
+    }
+
+    public boolean resume() {
+        if (isRecording) {
+            throw new IllegalStateException("[AMRAudioRecorder] recorder is recording!");
+        }
+
+        singleFile = false;
+        newRecorder();
+        return start();
+    }
+
+    public boolean stop() {
+        if (!isRecording) {
+            return merge();
+        }
+
+        if (recorder == null) {
+            return false;
+        }
+
+        recorder.stop();
+        recorder.release();
+        recorder = null;
+        isRecording = false;
+
+        return merge();
+    }
+
+    public void clear()
+    {
+        if (recorder != null || isRecording) {
+            recorder.stop();
+            recorder.release();
+            recorder = null;
+            isRecording = false;
+        }
+        for (int i = 0,len = files.size();i<len;i++) {
+            File file = new File(this.files.get(i));
+            file.delete();
+        }
+    }
+
+    private boolean merge() {
+
+        // If never paused, just return the file
+        if (singleFile) {
+            this.finalAudioPath = this.files.get(0);
+            return true;
+        }
+
+        // Merge files
+        String mergedFilePath = this.fileDirectory + new Date().getTime() + ".amr";
+        try {
+            FileOutputStream fos = new FileOutputStream(mergedFilePath);
+
+            for (int i = 0,len = files.size();i<len;i++) {
+                File file = new File(this.files.get(i));
+                FileInputStream fis = new FileInputStream(file);
+
+                // Skip file header bytes,
+                // amr file header's length is 6 bytes
+                if (i > 0) {
+                    for (int j=0; j<6; j++) {
+                        fis.read();
+                    }
+                }
+
+                byte[] buffer = new byte[512];
+                int count = 0;
+                while ( (count = fis.read(buffer)) != -1 ) {
+                    fos.write(buffer,0,count);
+                }
+
+                fis.close();
+                fos.flush();
+                file.delete();
+            }
+
+            fos.flush();
+            fos.close();
+
+            this.finalAudioPath = mergedFilePath;
+            return true;
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    private void newRecorder() {
+        recorder = new MediaRecorder();
+    }
+
+    private void prepareRecorder() {
+        File directory = new File(this.fileDirectory);
+        if (!directory.exists() || !directory.isDirectory()) {
+            throw new IllegalArgumentException("[AMRAudioRecorder] audioFileDirectory is a not valid directory!");
+        }
+
+        String filePath = directory.getAbsolutePath() + "/" + new Date().getTime() + ".amr";
+        Log.d("123",filePath);
+        this.files.add(filePath);
+
+        recorder.setOutputFile(filePath);
+        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
+        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+    }
+}

+ 3 - 0
amraudiorecorder/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">AMRAudioRecorder</string>
+</resources>

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 27 - 0
app/build.gradle

@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+
+    defaultConfig {
+        applicationId "com.water.example"
+        minSdkVersion 14
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    testImplementation 'junit:junit:4.12'
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+    implementation 'com.google.android.material:material:1.1.0-alpha01'
+    implementation project(':amraudiorecorder')
+}

+ 17 - 0
app/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/WaterZhang/Documents/MacAndroid/android-sdk-macosx/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 27 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.water.example">
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+
+        <activity android:name=".MainActivity" android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".PlaybackActivity" android:screenOrientation="portrait"/>
+    </application>
+
+</manifest>

+ 51 - 0
app/src/main/java/com/water/example/EasyTimer.java

@@ -0,0 +1,51 @@
+package com.water.example;
+
+import android.os.Handler;
+
+/**
+ * A convenient way to use timer.
+ *
+ * Created by Water Zhang on 12/9/13.
+ */
+public class EasyTimer {
+    private Handler handler;
+    private Runnable runnable;
+    private long delayInterval;
+    private CallBack callBack;
+    private boolean stopped;
+
+    public interface CallBack {
+        void execute();
+    }
+
+    public EasyTimer (final long millionSeconds,final CallBack callBack) {
+        this.delayInterval = millionSeconds;
+        this.callBack = callBack;
+        start();
+    }
+
+    public void stop  () {
+        stopped = true;
+        handler.removeCallbacksAndMessages(null);
+    }
+
+    public void restart () {
+        stop();
+        start();
+    }
+
+    private void start () {
+        stopped = false;
+        handler = new Handler();
+        runnable = new Runnable() {
+            @Override
+            public void run() {
+                if (!stopped) {
+                    callBack.execute();
+                    handler.postDelayed(runnable,delayInterval);
+                }
+            }
+        };
+        handler.postDelayed(runnable,delayInterval);
+    }
+}

+ 157 - 0
app/src/main/java/com/water/example/MainActivity.java

@@ -0,0 +1,157 @@
+package com.water.example;
+
+import android.Manifest;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import com.water.amraudiorecorder.AMRAudioRecorder;
+import com.water.example.utils.PermissionUtils;
+import com.water.example.utils.PermissionsDialogue;
+
+import java.io.File;
+
+public class MainActivity extends AppCompatActivity {
+
+    private int mRecordTimeInterval;
+
+    private TextView mRecordingTime;
+    private AMRAudioRecorder mRecorder;
+    private EasyTimer mAudioTimeLabelUpdater;
+    private PermissionsDialogue.Builder alertPermissions;
+    private Context mContext;
+
+    private String[] permissionsList = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        mContext = getApplicationContext();
+        mRecordingTime = (TextView) findViewById(R.id.recordingTime);
+        //Request permissions on Marshmallow and above
+        if (Build.VERSION.SDK_INT >= 23) {
+            if (!PermissionUtils.IsPermissionsEnabled(mContext, permissionsList))
+            {
+                alertPermissions = new PermissionsDialogue.Builder(this)
+                        .setMessage("AMRAudioRecorder records audio and requires the following permissions: ")
+                        .setRequireStorage(PermissionsDialogue.REQUIRED)
+                        .setRequireAudio(PermissionsDialogue.REQUIRED)
+                        .setOnContinueClicked(new PermissionsDialogue.OnContinueClicked() {
+                            @Override
+                            public void OnClick(View view, Dialog dialog) {
+                                dialog.dismiss();
+                            }
+                        })
+                        .setCancelable(false)
+                        .build();
+                alertPermissions.show();
+            }
+        }
+    }
+
+    public void viewOnClick(View view) {
+        switch (view.getId()) {
+
+            case R.id.toggleRecord:
+
+                if (mRecorder == null) {
+
+                    resetRecording();
+
+                    String sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+                    String recordingDirectory = sdcardPath + "/wtrecorder/";
+                    File dir = new File(recordingDirectory);
+                    if (!dir.exists()) {
+                        dir.mkdirs();
+                    }
+
+                    mRecorder = new AMRAudioRecorder(recordingDirectory);
+                    mRecorder.start();
+
+                    ((ImageButton) view).setImageResource(R.drawable.ic_pause);
+
+                    mAudioTimeLabelUpdater = new EasyTimer(1000, new EasyTimer.CallBack() {
+                        @Override
+                        public void execute() {
+                            int time = mRecordTimeInterval;
+
+                            int min = time / 60,sec = time % 60;
+
+                            String minStr = min < 10 ? "0"+min : ""+min;
+                            String secStr = sec < 10 ? "0"+sec : ""+sec;
+
+                            mRecordingTime.setText(minStr + ":"+secStr);
+
+                            mRecordTimeInterval ++;
+                        }
+                    });
+                }
+                else {
+                    if (mRecorder.isRecording()) {
+                        ((ImageButton) view).setImageResource(R.drawable.ic_play);
+
+                        mAudioTimeLabelUpdater.stop();
+                        mRecorder.pause();
+                    }
+                    else {
+                        ((ImageButton) view).setImageResource(R.drawable.ic_pause);
+
+                        mRecorder.resume();
+                        mAudioTimeLabelUpdater.restart();
+                    }
+                }
+
+                break;
+
+            case R.id.done:
+
+                if (mRecorder == null) {
+                    return;
+                }
+
+                mRecorder.stop();
+
+                Intent intent = new Intent(this, PlaybackActivity.class);
+                intent.putExtra("audioFilePath", mRecorder.getAudioFilePath());
+                startActivity(intent);
+
+                mRecorder = null;
+                resetRecording();
+
+                break;
+
+            case R.id.trash:
+
+                if (mRecorder == null) {
+                    return;
+                }
+
+                mRecorder.clear();
+                mRecorder = null;
+                resetRecording();
+
+                break;
+        }
+    }
+
+    private void resetRecording () {
+        if (mAudioTimeLabelUpdater != null) {
+            mAudioTimeLabelUpdater.stop();
+            mAudioTimeLabelUpdater = null;
+        }
+
+        mRecordTimeInterval = 0;
+        mRecordingTime.setText("00:00");
+
+        ((ImageButton)findViewById(R.id.toggleRecord)).setImageResource(R.drawable.ic_play);
+    }
+}

+ 63 - 0
app/src/main/java/com/water/example/PlaybackActivity.java

@@ -0,0 +1,63 @@
+package com.water.example;
+
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import java.io.File;
+
+public class PlaybackActivity extends AppCompatActivity {
+
+    private MediaPlayer mPlayer;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_playback);
+
+        String audioFilePath = getIntent().getStringExtra("audioFilePath");
+        mPlayer = new MediaPlayer();
+// 获取文件对象
+        File audioFile = new File(audioFilePath);
+
+// 获取文件大小(字节)
+        long fileSizeInBytes = audioFile.length();
+
+// 将文件大小转换为兆字节(MB)
+        double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
+// 四舍五入到两位小数
+        double roundedFileSizeInMB = Math.round(fileSizeInMB * 100.0) / 100.0;
+
+// 打印结果
+        Log.d("File Size", "File size: " + roundedFileSizeInMB + " MB");
+        Toast.makeText(this, "File Size"+roundedFileSizeInMB+"M", Toast.LENGTH_SHORT).show();
+        try {
+            mPlayer.setDataSource(audioFilePath);
+            mPlayer.prepare();
+            mPlayer.start();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    @Override
+    protected void onDestroy() {
+        stopPlayback(null);
+        super.onDestroy();
+    }
+
+    public void stopPlayback(View view) {
+        if (mPlayer != null) {
+            mPlayer.stop();
+            mPlayer.release();
+            mPlayer = null;
+        }
+
+        finish();
+    }
+}

+ 74 - 0
app/src/main/java/com/water/example/utils/BounceInterpolator.java

@@ -0,0 +1,74 @@
+package com.water.example.utils;
+
+//
+// Interpolator to be used with Android view Animation class to achieve the spring-bounce effect.
+//
+// License: MIT
+// Source: http://evgenii.com/blog/spring-button-animation-on-android/
+//
+// Usage example, make the button wobble in scale:
+// ------------
+//
+//    // Load animation
+//    final Animation myAnim = AnimationUtils.loadAnimation(this, R.anim.bounce);
+//    double animationDuration = getDurationValue() * 1000;
+//
+//    // Create interpolator with the amplitude 0.2 and frequency 20
+//    MyBounceInterpolator interpolator = new MyBounceInterpolator(0.2, 20);
+//
+//    myAnim.setInterpolator(interpolator);
+//    Button button = (Button)findViewById(R.id.button_to_animate);
+//    button.startAnimation(myAnim);
+//
+// anim/bounce.xml file:
+// --------------------
+//
+//    <?xml version="1.0" encoding="utf-8"?>
+//    <set xmlns:android="http://schemas.android.com/apk/res/android" >
+//
+//    <scale
+//    android:duration="2000"
+//            android:fromXScale="0.3"
+//            android:toXScale="1.0"
+//            android:fromYScale="0.3"
+//            android:toYScale="1.0"
+//            android:pivotX="50%"
+//            android:pivotY="50%" />
+//    </set>
+//
+//
+public class BounceInterpolator implements android.view.animation.Interpolator {
+    /**
+     * The amplitude of the bounces. The higher value (10, for example) produces more pronounced bounces.
+     * The lower values (0.1, for example) produce less noticeable wobbles.
+     */
+    double mAmplitude = 1;
+
+    /**
+     * The frequency of the bounces. The higher value produces more wobbles during the animation time period.
+     */
+    double mFrequency = 10;
+
+    /**
+     * Initialize a new interpolator.
+     *
+     * @param      amplitude   The amplitude of the bounces. The higher value produces more pronounced bounces. The lower values (0.1, for example) produce less noticeable wobbles.
+     * @param      frequency   The frequency of the bounces. The higher value produces more wobbles during the animation time period.
+     *
+     */
+    public BounceInterpolator(double amplitude, double frequency) {
+        mAmplitude = amplitude;
+        mFrequency = frequency;
+    }
+
+    public float getInterpolation(float time) {
+        double amplitude = mAmplitude;
+        if (amplitude == 0) { amplitude = 0.05; }
+
+        // The interpolation curve equation:
+        //    -e^(-time / amplitude) * cos(frequency * time) + 1
+        //
+        // View the graph live: https://www.desmos.com/calculator/6gbvrm5i0s
+        return (float) (-1 * Math.pow(Math.E, -time/ mAmplitude) * Math.cos(mFrequency * time) + 1);
+    }
+}

+ 208 - 0
app/src/main/java/com/water/example/utils/CustomButton.java

@@ -0,0 +1,208 @@
+package com.water.example.utils;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ImageSpan;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.water.example.R;
+
+public class CustomButton extends androidx.appcompat.widget.AppCompatTextView {
+    private GradientDrawable gradientDrawable;
+    private int mUnPressColor;
+    private int mPressColor;
+    private int mStrokeColor;
+    private int mStrokeWidth = 2;
+    private int mCornerRadius = 12;
+    private Resources resources;
+    private int defaultStrokeWidth = 2;
+    private int defaultCornerRadius = 12;
+    private int textUnPressColor;
+    private int textPressColor;
+    private int showDrawable;
+    private Drawable drawable;
+
+
+    public CustomButton(Context context) {
+        this(context,null,0,0);
+    }
+
+    public CustomButton(Context context, AttributeSet attrs) {
+        this(context,attrs, 0,0);
+    }
+
+    public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public CustomButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs,defStyleAttr,defStyleRes);
+    }
+
+    private void init(Context context, AttributeSet attrs,int defStyleAttr, int defStyleRes) {
+        resources = getResources();
+        if (gradientDrawable == null) {
+            gradientDrawable = new GradientDrawable();
+        }
+
+        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomButton, defStyleAttr, defStyleRes);
+        textUnPressColor = typedArray.getColor(R.styleable.CustomButton_btn_text_unpressColor, Color.GRAY);
+        textPressColor = typedArray.getColor(R.styleable.CustomButton_btn_text_pressColor, Color.WHITE);
+        mUnPressColor = typedArray.getColor(R.styleable.CustomButton_btn_unpressColor, Color.WHITE);
+        mPressColor = typedArray.getColor(R.styleable.CustomButton_btn_pressColor, Color.GRAY);
+        mStrokeColor = typedArray.getColor(R.styleable.CustomButton_btn_strokeColor, Color.GRAY);
+        mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CustomButton_btn_strokeWidth, defaultStrokeWidth);
+        mCornerRadius = typedArray.getDimensionPixelSize(R.styleable.CustomButton_btn_cornerRadius, defaultCornerRadius);
+        gradientDrawable.setShape(GradientDrawable.RECTANGLE);
+        gradientDrawable.setColor(mUnPressColor);
+        gradientDrawable.setStroke(mStrokeWidth, mStrokeColor, 0, 0);
+        gradientDrawable.setCornerRadius(mCornerRadius);
+
+        setButtonBackgroud();
+        typedArray.recycle();
+
+        setGravity(Gravity.CENTER);
+        setTextColor(textUnPressColor);
+
+        setOnTouchListener(new OnButtonTouchListener());
+    }
+
+    private void setButtonBackgroud() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+            setBackground(gradientDrawable);
+        else
+            setBackgroundDrawable(gradientDrawable);
+    }
+
+    class OnButtonTouchListener implements OnTouchListener {
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    setPressStatus(true);
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    setPressStatus(false);
+                    break;
+            }
+
+            return false;
+        }
+    }
+
+    public void setPressStatus(boolean isPress) {
+        if (isPress) {
+            setTextColor(textPressColor);
+            gradientDrawable.setColor(mPressColor);
+        } else {
+            setTextColor(textUnPressColor);
+            gradientDrawable.setColor(mUnPressColor);
+        }
+        setButtonBackgroud();
+    }
+
+
+    public void setButtonStatus(boolean isEnable) {
+        if (isEnable) {
+            setTextColor(textPressColor);
+            gradientDrawable.setColor(mPressColor);
+        } else {
+            setTextColor(textUnPressColor);
+            gradientDrawable.setColor(mUnPressColor);
+        }
+
+        setButtonBackgroud();
+    }
+
+    public void setDrawableRightText(int text, int imgResId) {
+        setDrawableRightText(resources.getString(text), imgResId);
+    }
+
+    public void setDrawableRightText(CharSequence text, int imgResId) {
+        Drawable drawable = resources.getDrawable(imgResId);
+        setDrawableRightText(text, drawable);
+    }
+
+    public void setDrawableRightText(CharSequence text, Drawable drawable) {
+        setText("");
+        SpannableString ss = new SpannableString("pics");
+
+        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
+        ss.setSpan(span, 0, ss.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+        append(text);
+        append(" ");
+        append(ss);
+    }
+
+    public void setDrawableLeftText(int text, int imgResId) {
+        setDrawableLeftText(resources.getString(text), imgResId);
+    }
+
+    public void setDrawableLeftText(CharSequence text, int imgResId) {
+        Drawable drawable = resources.getDrawable(imgResId);
+        setDrawableLeftText(text,drawable);
+    }
+
+    public void setDrawableLeftText(CharSequence text, Drawable drawable){
+        setText("");
+        SpannableString ss = new SpannableString("pics");
+        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
+        ss.setSpan(span, 0, ss.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+        append(ss);
+        append(" ");
+        append(text);
+    }
+
+    public void setColor(int textnormal, int textpressed, int buttonunpressed, int buttonpressed, int stroke)
+    {
+        textUnPressColor = textnormal;
+        textPressColor = textpressed;
+        mUnPressColor = buttonunpressed;
+        mPressColor = buttonpressed;
+        mStrokeColor = stroke;
+        gradientDrawable.setShape(GradientDrawable.RECTANGLE);
+        gradientDrawable.setColor(mUnPressColor);
+        gradientDrawable.setStroke(mStrokeWidth, mStrokeColor, 0, 0);
+        gradientDrawable.setCornerRadius(mCornerRadius);
+        setButtonBackgroud();
+        setTextColor(textUnPressColor);
+    }
+
+    /**
+     * 颜色加深处理
+     *
+     * @param RGBValues RGB的值,由alpha(透明度)、red(红)、green(绿)、blue(蓝)构成,
+     *                  Android中我们一般使用它的16进制,
+     *                  例如:"#FFAABBCC",最左边到最右每两个字母就是代表alpha(透明度)、
+     *                  red(红)、green(绿)、blue(蓝)。每种颜色值占一个字节(8位),值域0~255
+     *                  所以下面使用移位的方法可以得到每种颜色的值,然后每种颜色值减小一下,在合成RGB颜色,颜色就会看起来深一些了
+     * @return
+     */
+    private int colorBurn(int RGBValues) {
+        //int alpha = RGBValues >> 24;
+        int red = RGBValues >> 16 & 0xFF;
+        int green = RGBValues >> 8 & 0xFF;
+        int blue = RGBValues & 0xFF;
+        red = (int) Math.floor(red * (1 - 0.1));
+        green = (int) Math.floor(green * (1 - 0.1));
+        blue = (int) Math.floor(blue * (1 - 0.1));
+        return Color.rgb(red, green, blue);
+    }
+}

+ 38 - 0
app/src/main/java/com/water/example/utils/PermissionUtils.java

@@ -0,0 +1,38 @@
+package com.water.example.utils;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+public class PermissionUtils {
+
+    public static boolean IsPermissionEnabled(Context context, String permission)
+    {
+        if (Build.VERSION.SDK_INT >= 23) {
+            if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    public static boolean IsPermissionsEnabled(Context context, String[] permissionsList)
+    {
+        for (String permission : permissionsList)
+        {
+            if (!IsPermissionEnabled(context, permission))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

+ 1169 - 0
app/src/main/java/com/water/example/utils/PermissionsDialogue.java

@@ -0,0 +1,1169 @@
+package com.water.example.utils;
+
+/*
+ * Created by Ray Li
+ * © Copyright 2017 Ray LI
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.core.content.ContextCompat;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.water.example.R;
+
+import java.util.ArrayList;
+
+public class PermissionsDialogue extends DialogFragment {
+
+    public static Integer NOTREQUIRED = 0;
+    public static Integer REQUIRED = 1;
+    public static Integer OPTIONAL = 2;
+    public static String REQUIRE_PHONE = "REQUIRE_PHONE";
+    public static String REQUIRE_SMS = "REQUIRE_SMS";
+    public static String REQUIRE_CONTACTS = "REQUIRE_CONTACTS";
+    public static String REQUIRE_CALENDAR = "REQUIRE_CALENDAR";
+    public static String REQUIRE_STORAGE = "REQUIRE_STORAGE";
+    public static String REQUIRE_CAMERA = "REQUIRE_CAMERA";
+    public static String REQUIRE_AUDIO = "REQUIRE_AUDIO";
+    public static String REQUIRE_LOCATION = "REQUIRE_LOCATION";
+    private static final int REQUEST_PERMISSIONS = 1;
+    private static final int REQUEST_PERMISSION = 2;
+
+    private Button mButton;
+    private CustomPermissionsAdapter requiredAdapter;
+    private CustomPermissionsAdapter optionalAdapter;
+    private ArrayList<String> requiredPermissions;
+    private ArrayList<String> optionalPermissions;
+
+    private Context mContext;
+    private Builder builder;
+    private Integer gravity = Gravity.CENTER;
+    private static PermissionsDialogue instance = new PermissionsDialogue();
+    public static final String TAG = PermissionsDialogue.class.getSimpleName();
+
+    public static PermissionsDialogue getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+
+        setStyle(DialogFragment.STYLE_NO_TITLE, R.style.PermissionsDialogue);
+        setRetainInstance(true);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (builder != null)
+            outState.putParcelable(Builder.class.getSimpleName(), builder);
+    }
+
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        //Configure floating window
+        Window window = dialog.getWindow();
+        WindowManager.LayoutParams wlp = window.getAttributes();
+        wlp.width = WindowManager.LayoutParams.MATCH_PARENT; //Floating window WRAPS_CONTENT by default. Force fullscreen
+        wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
+        wlp.windowAnimations = R.style.CustomDialogAnimation;
+        wlp.gravity = Gravity.CENTER;
+        window.setAttributes(wlp);
+
+        if (builder != null) {
+            if (!builder.getCancelable()) {
+                setCancelable(false);
+            } else {
+                setCancelable(true);
+            }
+        }
+
+        return dialog;
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.alert_permissions, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mButton = (Button) getView().findViewById(R.id.permissions_btn);
+        mContext = getContext();
+
+        initPermissionsView(view);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+        Log.d("Request Code", String.valueOf(requestCode));
+        switch (requestCode) {
+            case REQUEST_PERMISSIONS: {
+                Log.d("Permissons", "Request Permissions");
+                refreshRequiredPermissions();
+                boolean permissionsGranted = true;
+                if (grantResults.length > 0) {
+                    for (int i = 0; i < grantResults.length; i++) {
+                        if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
+                            boolean showRationale = shouldShowRequestPermissionRationale(permissions[i]);
+                            if (!showRationale) {
+                                permissionsGranted = false;
+                            }
+                        }
+                    }
+                }
+
+                if (permissionsGranted) {
+                    refreshPermissionsButton(false);
+                    Log.d("Permissions", "Granted");
+                } else {
+                    refreshPermissionsButton(true);
+                    Log.d("Permissions", "Denied");
+                }
+                return;
+            }
+
+            case REQUEST_PERMISSION: {
+                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                    refreshOptionalPermissions();
+                } else {
+
+                }
+                return;
+            }
+        }
+    }
+
+    private void initPermissionsView(View view)
+    {
+        if (builder != null) {
+
+            Log.d(TAG, "Builder Not Null");
+
+            if (builder.getRequiredPermissions().size() != 0)
+            {
+                initPermissionsRecyclerView(view);
+                initPermissionsButton(view);
+            }
+            else
+            {
+                LinearLayout permissionsLayout = (LinearLayout) view.findViewById(R.id.permissions_required);
+                permissionsLayout.setVisibility(View.GONE);
+            }
+
+            if (builder.getOptionalPermissions().size() != 0)
+            {
+                initOptionalPermissionsRecyclerView(view);
+            }
+            else
+            {
+                LinearLayout permissionsLayout = (LinearLayout) view.findViewById(R.id.permissions_optional);
+                permissionsLayout.setVisibility(View.GONE);
+            }
+        }
+        else
+        {
+            Log.d(TAG, "Builder Null");
+        }
+    }
+
+    private void initPermissionsRecyclerView(View view) {
+        if (builder.getTitle() != null)
+        {
+            TextView title = (TextView) view.findViewById(R.id.title);
+            title.setText(builder.getTitle());
+        }
+        if (builder.getMessage() != null)
+        {
+            TextView message = (TextView) view.findViewById(R.id.message);
+            message.setText(builder.getMessage());
+        }
+        else
+        {
+            TextView message = (TextView) view.findViewById(R.id.message);
+            message.setVisibility(View.GONE);
+        }
+        if (builder.getIcon() == false)
+        {
+            ImageView icon = (ImageView) view.findViewById(R.id.icon);
+            icon.setVisibility(View.GONE);
+        }
+
+        requiredPermissions = new ArrayList<>();
+        requiredPermissions = builder.getRequiredPermissions();
+        int spanSize = requiredPermissions.size();
+        RecyclerView permissionsRecyclerView = (RecyclerView) view.findViewById(R.id.permissions_list);
+        requiredAdapter = new CustomPermissionsAdapter(mContext, requiredPermissions, false);
+        permissionsRecyclerView.setAdapter(requiredAdapter);
+        GridLayoutManager layoutManager= new GridLayoutManager(mContext, spanSize, LinearLayoutManager.VERTICAL, false);
+        permissionsRecyclerView.setLayoutManager(layoutManager);
+    }
+
+    private void initOptionalPermissionsRecyclerView(View view) {
+        optionalPermissions = new ArrayList<>();
+        optionalPermissions = builder.getOptionalPermissions();
+        int spanSize = optionalPermissions.size();
+        if (spanSize > 2)
+        {
+            spanSize = 2;
+        }
+        RecyclerView permissionsRecyclerView = (RecyclerView) view.findViewById(R.id.permissions_list_optional);
+        optionalAdapter = new CustomPermissionsAdapter(mContext, optionalPermissions, true);
+        permissionsRecyclerView.setAdapter(optionalAdapter);
+        GridLayoutManager layoutManager= new GridLayoutManager(mContext, spanSize, LinearLayoutManager.VERTICAL, false);
+        permissionsRecyclerView.setLayoutManager(layoutManager);
+        int spacing = Units.dpToPx(mContext, 40);
+        boolean includeEdge = true;
+        permissionsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(spanSize, spacing, includeEdge));
+    }
+
+    private void initPermissionsButton(View view) {
+        mButton = (Button) view.findViewById(R.id.permissions_btn);
+        if (builder.getRequiredRequestPermissions().size() == 0)
+        {
+            mButton.setText("Continue");
+            mButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.icon_add_activated));
+            if (builder.getOnContinueClicked() != null)
+            {
+                mButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        builder.getOnContinueClicked().OnClick(getView(), getDialog());
+                    }
+                });
+            }
+            else
+            {
+                mButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        mButton.startAnimation(AnimateButton());
+                        Handler handler = new Handler();
+                        Runnable r = new Runnable() {
+                            public void run() {
+                                dismiss();
+                            }
+                        };
+                        handler.postDelayed(r, 250);
+                    }
+                });
+            }
+        }
+        else
+        {
+            mButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    mButton.startAnimation(AnimateButton());
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                        ArrayList<String> requestPermissions = builder.getRequiredRequestPermissions();
+                        if (!requestPermissions.isEmpty()) {
+                            requestPermissions(requestPermissions.toArray(new String[requestPermissions.size()]), REQUEST_PERMISSIONS);
+                        }
+                        else
+                        {
+
+                        }
+                    } else {
+
+                    }
+                }
+            });
+        }
+    }
+
+    private void refreshRequiredPermissions()
+    {
+        requiredPermissions = builder.getRequiredPermissions();
+        requiredAdapter.permissionsList = requiredPermissions;
+        requiredAdapter.notifyDataSetChanged();
+    }
+
+    private void refreshOptionalPermissions()
+    {
+        optionalPermissions = builder.getOptionalPermissions();
+        optionalAdapter.permissionsList = optionalPermissions;
+        optionalAdapter.notifyDataSetChanged();
+    }
+
+    private void refreshPermissionsButton(boolean denied)
+    {
+        if (denied)
+        {
+            mButton.setText("DENIED - Open Settings");
+            mButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.icon_add_error));
+            mButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    dismiss();
+                    Toast.makeText(mContext, "Click Permissions to enable permissions", Toast.LENGTH_LONG).show();
+                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+                            Uri.parse("package:" + getActivity().getPackageName()));
+                    intent.addCategory(Intent.CATEGORY_DEFAULT);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    startActivity(intent);
+                }
+            });
+        }
+        else if (builder.getRequiredRequestPermissions().size() == 0)
+        {
+            mButton.setText("Success!");
+            mButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.icon_add_activated));
+            if (builder.getOnContinueClicked() != null)
+            {
+                mButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        builder.getOnContinueClicked().OnClick(getView(), getDialog());
+                    }
+                });
+            }
+            else
+            {
+                mButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        mButton.startAnimation(AnimateButton());
+                        Handler handler = new Handler();
+                        Runnable r = new Runnable() {
+                            public void run() {
+                                dismiss();
+                            }
+                        };
+                        handler.postDelayed(r, 250);
+                    }
+                });
+            }
+
+            Handler handler = new Handler();
+            Runnable r = new Runnable() {
+                public void run() {
+                    mButton.setText("Continue");
+                }
+            };
+            handler.postDelayed(r, 1500);
+        }
+        else {
+            mButton.setText("Permission Denied");
+            mButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.icon_add_error));
+            mButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    mButton.startAnimation(AnimateButton());
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                        ArrayList<String> requestPermissions = builder.getRequiredRequestPermissions();
+                        if (!requestPermissions.isEmpty()) {
+                            requestPermissions(requestPermissions.toArray(new String[requestPermissions.size()]), REQUEST_PERMISSIONS);
+                        } else {
+
+                        }
+                    } else {
+
+                    }
+                }
+            });
+
+            Handler handler = new Handler();
+            Runnable r = new Runnable() {
+                public void run() {
+                    mButton.setText("Grant Permissions");
+                    mButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.icon_add));
+                }
+            };
+            handler.postDelayed(r, 1500);
+        }
+        mButton.startAnimation(AnimateButton());
+    }
+
+    private Dialog show(Activity activity, Builder builder) {
+        this.builder = builder;
+        if (!isAdded())
+            show(((AppCompatActivity) activity).getSupportFragmentManager(), TAG);
+        return getDialog();
+    }
+
+    public static class Builder implements Parcelable {
+
+        private String title;
+        private String message;
+
+        private OnContinueClicked onContinueClicked;
+
+        private boolean autoHide;
+        private boolean cancelable = true;
+        private boolean showIcon = true;
+        private Integer phone = 0;
+        private Integer sms = 0;
+        private Integer contacts = 0;
+        private Integer calendar = 0;
+        private Integer storage = 0;
+        private Integer camera = 0;
+        private Integer audio = 0;
+        private Integer location = 0;
+        private String phonedescription;
+        private String smsdescription;
+        private String contactsdescription;
+        private String calendardescription;
+        private String storagedescription;
+        private String cameradescription;
+        private String audiodescription;
+        private String locationdescription;
+
+        private Context context;
+
+        protected Builder(Parcel in) {
+            title = in.readString();
+            message = in.readString();
+            autoHide = in.readByte() != 0;
+            cancelable = in.readByte() != 0;
+            phone = in.readInt();
+            sms = in.readInt();
+            contacts = in.readInt();
+            calendar = in.readInt();
+            storage = in.readInt();
+            camera = in.readInt();
+            audio = in.readInt();
+            location = in.readInt();
+        }
+
+        public static final Creator<Builder> CREATOR = new Creator<Builder>() {
+            @Override
+            public Builder createFromParcel(Parcel in) {
+                return new Builder(in);
+            }
+
+            @Override
+            public Builder[] newArray(int size) {
+                return new Builder[size];
+            }
+        };
+
+        public Builder setContext(Context context) {
+            this.context = context;
+            return this;
+        }
+        public Context getContext() {
+            return context;
+        }
+
+        public Builder getBuilder() { return this; }
+
+        public Builder setTitle(String title) {
+            this.title = title;
+            return this;
+        }
+        public String getTitle() { return title; }
+
+        public Builder setMessage(String message) {
+            this.message = message;
+            return this;
+        }
+        public String getMessage() {
+            return message;
+        }
+
+        public Builder setOnContinueClicked(OnContinueClicked onContinueClicked) {
+            this.onContinueClicked = onContinueClicked;
+            return this;
+        }
+        public OnContinueClicked getOnContinueClicked() {
+            return onContinueClicked;
+        }
+
+        public Builder setAutoHide(boolean autoHide) {
+            this.autoHide = autoHide;
+            return this;
+        }
+        public boolean isAutoHide() {
+            return autoHide;
+        }
+
+        public Builder setCancelable(boolean cancelable) {
+            this.cancelable = cancelable;
+            return this;
+        }
+        public boolean getCancelable() { return cancelable; }
+
+        public Builder setIcon(boolean showicon) {
+            this.showIcon = showicon;
+            return this;
+        }
+        public boolean getIcon() { return showIcon; }
+
+        public Builder setActivity(Context context) {
+            this.context = context;
+            return this;
+        }
+
+        public Builder setRequirePhone(Integer phone) {
+            this.phone = phone;
+            return this;
+        }
+        public Integer requirePhone() {
+            return phone;
+        }
+
+        public Builder setRequireSMS(Integer sms) {
+            this.sms = sms;
+            return this;
+        }
+        public Integer requireSMS() {
+            return sms;
+        }
+
+        public Builder setRequireContacts(Integer contacts) {
+            this.contacts = contacts;
+            return this;
+        }
+        public Integer requireContacts() {
+            return contacts;
+        }
+
+        public Builder setRequireCalendar(Integer calendar) {
+            this.calendar = calendar;
+            return this;
+        }
+        public Integer requireCalendar() {
+            return calendar;
+        }
+
+        public Builder setRequireStorage(Integer storage) {
+            this.storage = storage;
+            return this;
+        }
+        public Integer requireStorage() {
+            return storage;
+        }
+
+        public Builder setRequireCamera(Integer camera) {
+            this.camera = camera;
+            return this;
+        }
+        public Integer requireCamera() {
+            return camera;
+        }
+
+        public Builder setRequireAudio(Integer audio) {
+            this.audio = audio;
+            return this;
+        }
+        public Integer requireAudio() {
+            return audio;
+        }
+
+        public Builder setRequireLocation(Integer location) {
+            this.location = location;
+            return this;
+        }
+        public Integer requireLocation() {
+            return location;
+        }
+
+        public Builder setPhoneDescription(String phonedescription) {
+            this.phonedescription = phonedescription;
+            return this;
+        }
+        public String getPhoneDescription() {
+            return phonedescription;
+        }
+
+        public Builder setSMSDescription(String smsdescription) {
+            this.smsdescription = smsdescription;
+            return this;
+        }
+        public String getSMSDescription() {
+            return smsdescription;
+        }
+
+        public Builder setContactDescription(String contactsdescription) {
+            this.contactsdescription = contactsdescription;
+            return this;
+        }
+        public String getContactDescription() {
+            return contactsdescription;
+        }
+
+        public Builder setCalendarDescription(String calendardescription) {
+            this.calendardescription = calendardescription;
+            return this;
+        }
+        public String getCalendarDescription() {
+            return calendardescription;
+        }
+
+        public Builder setStorageDescription(String storagedescription) {
+            this.storagedescription = storagedescription;
+            return this;
+        }
+        public String getStorageDescription() {
+            return storagedescription;
+        }
+
+        public Builder setCameraDescription(String cameradescription) {
+            this.cameradescription = cameradescription;
+            return this;
+        }
+        public String getCameraDescription() {
+            return cameradescription;
+        }
+
+        public Builder setAudioDescription(String audiodescription) {
+            this.audiodescription = audiodescription;
+            return this;
+        }
+        public String getAudioDescription() {
+            return audiodescription;
+        }
+
+        public Builder setLocationDescription(String locationdescription) {
+            this.locationdescription = locationdescription;
+            return this;
+        }
+        public String getLocationDescription() {
+            return locationdescription;
+        }
+
+        public ArrayList<String> getRequiredPermissions()
+        {
+            ArrayList<String> requiredPermissions = new ArrayList<>();
+            if (requirePhone() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_PHONE);
+            }
+            if (requireSMS() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_SMS);
+            }
+            if (requireContacts() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_CONTACTS);
+            }
+            if (requireCalendar() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_CALENDAR);
+            }
+            if (requireStorage() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_STORAGE);
+            }
+            if (requireCamera() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_CAMERA);
+            }
+            if (requireAudio() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_AUDIO);
+            }
+            if (requireLocation() == REQUIRED)
+            {
+                requiredPermissions.add(REQUIRE_LOCATION);
+            }
+            return requiredPermissions;
+        }
+
+        public ArrayList<String> getOptionalPermissions()
+        {
+            ArrayList<String> requiredPermissions = new ArrayList<>();
+            if (requirePhone() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_PHONE);
+            }
+            if (requireSMS() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_SMS);
+            }
+            if (requireContacts() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_CONTACTS);
+            }
+            if (requireCalendar() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_CALENDAR);
+            }
+            if (requireStorage() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_STORAGE);
+            }
+            if (requireCamera() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_CAMERA);
+            }
+            if (requireAudio() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_AUDIO);
+            }
+            if (requireLocation() == OPTIONAL)
+            {
+                requiredPermissions.add(REQUIRE_LOCATION);
+            }
+            return requiredPermissions;
+        }
+
+        public ArrayList<String> getRequiredRequestPermissions()
+        {
+            ArrayList<String> requestPermissions = new ArrayList<>();
+            if (requirePhone() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.CALL_PHONE)) {
+                    requestPermissions.add(Manifest.permission.CALL_PHONE);
+                }
+            }
+            if (requireSMS() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.SEND_SMS)) {
+                    requestPermissions.add(Manifest.permission.SEND_SMS);
+                }
+            }
+            if (requireContacts() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.WRITE_CONTACTS)) {
+                    requestPermissions.add(Manifest.permission.WRITE_CONTACTS);
+                }
+            }
+            if (requireCalendar() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.WRITE_CALENDAR)) {
+                    requestPermissions.add(Manifest.permission.WRITE_CALENDAR);
+                }
+            }
+            if (requireStorage() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+                    requestPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+                }
+            }
+            if (requireCamera() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.CAMERA)) {
+                    requestPermissions.add(Manifest.permission.CAMERA);
+                }
+            }
+            if (requireAudio() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.RECORD_AUDIO)) {
+                    requestPermissions.add(Manifest.permission.RECORD_AUDIO);
+                }
+            }
+            if (requireLocation() == REQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
+                    requestPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
+                }
+            }
+            return requestPermissions;
+        }
+
+        public ArrayList<String> getAllRequestPermissions()
+        {
+            ArrayList<String> requestPermissions = new ArrayList<>();
+            if (requirePhone() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.CALL_PHONE)) {
+                    requestPermissions.add(Manifest.permission.CALL_PHONE);
+                }
+            }
+            if (requireSMS() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.SEND_SMS)) {
+                    requestPermissions.add(Manifest.permission.SEND_SMS);
+                }
+            }
+            if (requireContacts() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.WRITE_CONTACTS)) {
+                    requestPermissions.add(Manifest.permission.WRITE_CONTACTS);
+                }
+            }
+            if (requireCalendar() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.WRITE_CALENDAR)) {
+                    requestPermissions.add(Manifest.permission.WRITE_CALENDAR);
+                }
+            }
+            if (requireStorage() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+                    requestPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+                }
+            }
+            if (requireCamera() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.CAMERA)) {
+                    requestPermissions.add(Manifest.permission.CAMERA);
+                }
+            }
+            if (requireAudio() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.RECORD_AUDIO)) {
+                    requestPermissions.add(Manifest.permission.RECORD_AUDIO);
+                }
+            }
+            if (requireLocation() > NOTREQUIRED) {
+                if (!PermissionUtils.IsPermissionEnabled(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
+                    requestPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
+                }
+            }
+            return requestPermissions;
+        }
+
+        public Builder(Context context) { this.context = context; }
+
+        public Builder build() {
+            return this;
+        }
+
+        public Dialog show() {
+            return PermissionsDialogue.getInstance().show(((Activity) context), this);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int i) {
+        }
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+    }
+
+    public interface OnContinueClicked {
+        void OnClick(View view, Dialog dialog);
+    }
+
+    public interface OnNegativeClicked {
+        void OnClick(View view, Dialog dialog);
+    }
+
+    public interface OnCancelClicked {
+        void OnClick(View view, Dialog dialog);
+    }
+
+    public Animation AnimateButton() {
+        // Load the animation
+        final Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.bounce);
+        double animationDuration = 1250;
+        animation.setDuration((long) animationDuration);
+
+        // Use custom animation interpolator to achieve the bounce effect
+        BounceInterpolator interpolator = new BounceInterpolator(0.2, 20);
+        animation.setInterpolator(interpolator);
+
+        return animation;
+    }
+
+    public class CustomPermissionsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+
+        public Context mContext;
+        public ArrayList<String> permissionsList;
+        public boolean optional;
+
+        public CustomPermissionsAdapter(Context context, ArrayList<String> permissionsList, boolean optional) {
+            this.mContext = context;
+            this.permissionsList = permissionsList;
+            this.optional = optional;
+        }
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            View itemView;
+            itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_permission, parent, false);
+            return new PermissionViewHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder genericHolder, final int position) {
+            String permission = permissionsList.get(position);
+            PermissionViewHolder holder = (PermissionViewHolder) genericHolder;
+            holder.setPermission(permission, optional);
+        }
+
+        @Override
+        public int getItemCount() { return permissionsList.size(); }
+
+        public String getPermission(int position) { return permissionsList.get(position); }
+    }
+
+    public class PermissionViewHolder extends RecyclerView.ViewHolder {
+
+        public String permission;
+        public TextView mName;
+        public TextView mMessage;
+        public ImageView mImage;
+        public CustomButton mButton;
+        public boolean optional;
+        public Context mContext;
+
+        public PermissionViewHolder(View itemView) {
+            super(itemView);
+
+            mName = (TextView) itemView.findViewById(R.id.permission_name);
+            mMessage = (TextView) itemView.findViewById(R.id.permission_detail);
+            mImage = (ImageView) itemView.findViewById(R.id.permission_icon);
+            mButton = (CustomButton) itemView.findViewById(R.id.permission_btn);
+            mContext = itemView.getContext();
+        }
+
+        public void setPermission(String permission, boolean optional) {
+            this.permission = permission;
+            this.optional = optional;
+
+            if (!optional)
+                mButton.setVisibility(View.GONE);
+
+            if (REQUIRE_PHONE.equals(permission))
+            {
+                mName.setText("Phone");
+                mImage.setImageResource(R.drawable.ic_phone);
+                setRequestPermissions(Manifest.permission.CALL_PHONE);
+
+                if (builder.getPhoneDescription() != null)
+                {
+                    mMessage.setText(builder.getPhoneDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_SMS.equals(permission))
+            {
+                mName.setText("SMS");
+                mImage.setImageResource(R.drawable.ic_text);
+
+                setRequestPermissions(Manifest.permission.SEND_SMS);
+
+                if (builder.getSMSDescription() != null)
+                {
+                    mMessage.setText(builder.getSMSDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_CONTACTS.equals(permission))
+            {
+                mName.setText("Contacts");
+
+                mImage.setImageResource(R.drawable.ic_contacts);
+
+                setRequestPermissions(Manifest.permission.WRITE_CONTACTS);
+
+                if (builder.getContactDescription() != null)
+                {
+                    mMessage.setText(builder.getContactDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_CALENDAR.equals(permission))
+            {
+                mName.setText("Calendar");
+                mImage.setImageResource(R.drawable.ic_calendar);
+
+                setRequestPermissions(Manifest.permission.WRITE_CALENDAR);
+
+                if (builder.getCalendarDescription() != null)
+                {
+                    mMessage.setText(builder.getCalendarDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_STORAGE.equals(permission))
+            {
+                mName.setText("Storage");
+                mImage.setImageResource(R.drawable.ic_folder);
+
+                setRequestPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+
+                if (builder.getStorageDescription() != null)
+                {
+                    mMessage.setText(builder.getStorageDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_CAMERA.equals(permission))
+            {
+                mName.setText("Camera");
+                mImage.setImageResource(R.drawable.ic_camera);
+
+                setRequestPermissions(Manifest.permission.CAMERA);
+
+                if (builder.getCameraDescription() != null)
+                {
+                    mMessage.setText(builder.getCameraDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_AUDIO.equals(permission))
+            {
+                mName.setText("Audio");
+                mImage.setImageResource(R.drawable.ic_mic);
+
+                setRequestPermissions(Manifest.permission.RECORD_AUDIO);
+
+                if (builder.getAudioDescription() != null)
+                {
+                    mMessage.setText(builder.getAudioDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else if (REQUIRE_LOCATION.equals(permission))
+            {
+                mName.setText("Location");
+                mImage.setImageResource(R.drawable.ic_location);
+
+                setRequestPermissions(Manifest.permission.ACCESS_FINE_LOCATION);
+
+                if (builder.getLocationDescription() != null)
+                {
+                    mMessage.setText(builder.getLocationDescription());
+                    mMessage.setVisibility(View.VISIBLE);
+                }
+                else
+                {
+                    mMessage.setVisibility(View.GONE);
+                }
+            }
+            else
+            {
+
+            }
+        }
+
+        public void setRequestPermissions(final String requestPermission)
+        {
+            if (PermissionUtils.IsPermissionEnabled(mContext, requestPermission))
+            {
+                int color = ContextCompat.getColor(mContext, R.color.button_pressed);
+                mImage.setColorFilter(color);
+                if (optional) {
+                    mButton.setText("Active");
+                    mButton.setColor(ContextCompat.getColor(mContext, R.color.white), ContextCompat.getColor(mContext, R.color.white),
+                            ContextCompat.getColor(mContext, R.color.green_light), ContextCompat.getColor(mContext, R.color.green), ContextCompat.getColor(mContext, R.color.green));
+                    mButton.setButtonStatus(true);
+                    mButton.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            mButton.setButtonStatus(true);
+                        }
+                    });
+                }
+            }
+            else
+            {
+                int color = ContextCompat.getColor(mContext, R.color.button_inactive);
+                mImage.setColorFilter(color);
+                if (optional)
+                {
+                    mButton.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                                requestPermissions(new String[]{requestPermission}, REQUEST_PERMISSION);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
+
+        private int spanCount;
+        private int spacing;
+        private boolean includeEdge;
+
+        public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
+            this.spanCount = spanCount;
+            this.spacing = spacing;
+            this.includeEdge = includeEdge;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+            int position = parent.getChildAdapterPosition(view); // item position
+            int column = position % spanCount; // item column
+
+            if (includeEdge) {
+                outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
+                outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
+
+//                if (position < spanCount) { // top edge
+//                    outRect.top = spacing/2;
+//                }
+                outRect.bottom = spacing/2; // item bottom
+            } else {
+                outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
+                outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
+                if (position >= spanCount) {
+                    outRect.top = spacing; // item top
+                }
+            }
+        }
+    }
+}

+ 36 - 0
app/src/main/java/com/water/example/utils/Units.java

@@ -0,0 +1,36 @@
+package com.water.example.utils;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+public class
+Units {
+
+    /**
+     * Converts dp to pixels.
+     */
+    public static int dpToPx(Context context, int dp) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
+        return px;
+    }
+
+    /**
+     * Converts pixels to dp.
+     */
+    public static int pxToDp(Context context, int px) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        int dp = Math.round(px / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
+        return dp;
+    }
+
+    public static float spToPx(Context context, float sp) {
+        float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
+        return sp * scaledDensity;
+    }
+
+    public static float pxToSp(Context context, float px) {
+        float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
+        return px / scaledDensity;
+    }
+}

+ 11 - 0
app/src/main/res/anim/bounce.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+    <scale
+        android:duration="2000"
+        android:fromXScale="0.3"
+        android:toXScale="1.0"
+        android:fromYScale="0.3"
+        android:toYScale="1.0"
+        android:pivotX="50%"
+        android:pivotY="50%" />
+</set>

+ 17 - 0
app/src/main/res/anim/fade_in_center.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:duration="300">
+
+  <scale
+      android:fromXScale="95%"
+      android:toXScale="100%"
+      android:fromYScale="95%"
+      android:toYScale="100%"
+      android:pivotX="50%"
+      android:pivotY="50%"/>
+
+  <alpha
+      android:fromAlpha="0"
+      android:toAlpha="1"/>
+</set>

+ 17 - 0
app/src/main/res/anim/fade_out_center.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:duration="300">
+
+  <scale
+      android:fromXScale="100%"
+      android:toXScale="95%"
+      android:fromYScale="100%"
+      android:toYScale="95%"
+      android:pivotX="50%"
+      android:pivotY="50%"/>
+
+  <alpha
+      android:fromAlpha="1"
+      android:toAlpha="0"/>
+</set>

+ 5 - 0
app/src/main/res/drawable/bg_translucent_white.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/alert_actionsheet_header"/>
+    <corners android:radius="@dimen/alert_radius_actionsheet"/>
+</shape>

+ 12 - 0
app/src/main/res/drawable/ic_calendar.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0-1.99 0.9 -1.99 2L3 19c0 1.1 0.89 2 2
+2h14c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2h-1V1h-2zm3 18H5V8h14v11z" />
+</vector>

+ 16 - 0
app/src/main/res/drawable/ic_camera.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M 12 8.8 C 13.7673111995 8.8 15.2 10.2326888005 15.2 12 C 15.2 13.7673111995 13.7673111995 15.2 12 15.2 C 10.2326888005 15.2 8.8 13.7673111995 8.8 12 C 8.8 10.2326888005 10.2326888005 8.8 12 8.8 Z" />
+    <path
+        android:fillColor="#757575"
+        android:pathData="M9 2L7.17 4H4c-1.1 0-2 0.9-2 2v12c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9
+2-2V6c0-1.1-0.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5
+5-2.24 5-5 5z" />
+</vector>

+ 14 - 0
app/src/main/res/drawable/ic_contacts.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M20 0H4v2h16V0zM4 24h16v-2H4v2zM20 4H4c-1.1 0-2 0.9-2 2v12c0 1.1 0.9 2 2 2h16c1.1
+0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 2.75c1.24 0 2.25 1.01 2.25 2.25s-1.01 2.25-2.25
+2.25S9.75 10.24 9.75 9 10.76 6.75 12 6.75zM17 17H7v-1.5c0-1.67 3.33-2.5 5-2.5s5
+0.83 5 2.5V17z" />
+</vector>

TEMPAT SAMPAH
app/src/main/res/drawable/ic_delete.png


TEMPAT SAMPAH
app/src/main/res/drawable/ic_done.png


+ 12 - 0
app/src/main/res/drawable/ic_folder.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M10 4H4c-1.1 0-1.99 0.9 -1.99 2L2 18c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9
+2-2V8c0-1.1-0.9-2-2-2h-8l-2-2z" />
+</vector>

+ 12 - 0
app/src/main/res/drawable/ic_location.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0
+9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z" />
+</vector>

+ 13 - 0
app/src/main/res/drawable/ic_mic.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3
+3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6
+6.72V21h2v-3.28c3.28-0.48 6-3.3 6-6.72h-1.7z" />
+</vector>

TEMPAT SAMPAH
app/src/main/res/drawable/ic_pause.png


+ 14 - 0
app/src/main/res/drawable/ic_phone.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c0.27-0.27 0.67 -0.36 1.02-0.24 1.12
+0.37 2.33 0.57 3.57 0.57 0.55 0 1 0.45 1 1V20c0 0.55-0.45 1-1 1-9.39 0-17-7.61-17-17
+0-0.55 0.45 -1 1-1h3.5c0.55 0 1 0.45 1 1 0 1.25 0.2 2.45 0.57 3.57 0.11 0.35 0.03 0.74-0.25
+1.02l-2.2 2.2z" />
+</vector>

TEMPAT SAMPAH
app/src/main/res/drawable/ic_play.png


+ 12 - 0
app/src/main/res/drawable/ic_text.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#757575"
+        android:pathData="M20 2H4c-1.1 0-1.99 0.9 -1.99 2L2 22l4-4h14c1.1 0 2-0.9 2-2V4c0-1.1-0.9-2-2-2zM9
+11H7V9h2v2zm4 0h-2V9h2v2zm4 0h-2V9h2v2z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/icon_add.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <corners
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp"
+        android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"/>
+    <solid android:color="@color/colorPrimary"/>
+</shape>

+ 9 - 0
app/src/main/res/drawable/icon_add_activated.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <corners
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp"
+        android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"/>
+    <solid android:color="@color/green"/>
+</shape>

+ 9 - 0
app/src/main/res/drawable/icon_add_error.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <corners
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp"
+        android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"/>
+    <solid android:color="@color/red"/>
+</shape>

+ 9 - 0
app/src/main/res/drawable/icon_add_pressed.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <corners
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp"
+        android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"/>
+    <solid android:color="@color/colorPrimaryLight"/>
+</shape>

+ 6 - 0
app/src/main/res/drawable/icon_add_selector.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/icon_add_pressed"/>
+    <item android:state_focused="true" android:drawable="@drawable/icon_add_pressed"/>
+    <item android:drawable="@drawable/icon_add"/>
+</selector>

+ 82 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout 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"
+    tools:context="com.water.example.MainActivity">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/holo_red_light"
+        >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text="Recording Time"
+            android:id="@+id/textView"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="56dp"
+            android:textColor="@android:color/white" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:text="00:00"
+            android:id="@+id/recordingTime"
+            android:layout_centerHorizontal="true"
+            android:layout_below="@+id/textView"
+            android:layout_marginTop="10dp"
+            android:gravity="center"
+            android:textColor="@android:color/white"
+            android:textSize="48sp"
+            android:typeface="monospace" />
+
+
+        <ImageButton
+            android:layout_width="80dp"
+            android:layout_height="80dp"
+            android:scaleType="fitXY"
+            android:id="@+id/toggleRecord"
+            android:src="@drawable/ic_play"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="40dp"
+            android:background="@android:color/transparent"
+            android:clickable="true"
+            android:onClick="viewOnClick" />
+
+        <ImageButton
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:scaleType="fitXY"
+            android:id="@+id/done"
+            android:src="@drawable/ic_done"
+            android:background="@android:color/transparent"
+            android:layout_alignParentRight="false"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="45dp"
+            android:layout_toRightOf="@+id/toggleRecord"
+            android:clickable="true"
+            android:onClick="viewOnClick"
+            android:layout_marginLeft="45dp" />
+
+        <ImageButton
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:scaleType="fitXY"
+            android:id="@+id/trash"
+            android:src="@drawable/ic_delete"
+            android:background="@android:color/transparent"
+            android:layout_alignParentRight="false"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="45dp"
+            android:layout_toLeftOf="@+id/toggleRecord"
+            android:clickable="true"
+            android:onClick="viewOnClick"
+            android:layout_marginRight="45dp" />
+    </RelativeLayout>
+</RelativeLayout>

+ 17 - 0
app/src/main/res/layout/activity_playback.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout 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"
+    tools:context="com.water.example.MainActivity">
+
+
+    <Button
+        android:layout_width="100dp"
+        android:layout_height="wrap_content"
+        android:text="Stop"
+        android:id="@+id/stop"
+        android:textColor="@android:color/holo_red_light"
+        android:layout_centerInParent="true"
+        android:onClick="stopPlayback" />
+</RelativeLayout>

+ 112 - 0
app/src/main/res/layout/alert_permissions.xml

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/permissions_required"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="@dimen/alert_dialogue_margin_side"
+        android:layout_marginRight="@dimen/alert_dialogue_margin_side"
+        android:layout_marginTop="@dimen/alert_dialogue_margin_top"
+        android:paddingTop="12dp"
+        android:paddingLeft="12dp"
+        android:paddingRight="12dp"
+        android:background="@drawable/bg_translucent_white"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/alert_text_margin_border"
+            android:layout_marginRight="@dimen/alert_text_margin_border"
+            android:gravity="center"
+            android:text="Permissions Manager"
+            android:textSize="@dimen/alert_text_title_size"
+            android:textColor="@color/alert_text_default"
+            android:fontFamily="sans-serif-medium"/>
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:layout_margin="18dp"
+            android:layout_gravity="center_horizontal"
+            android:src="@mipmap/ic_launcher"/>
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/alert_text_message_margin_bottom"
+            android:layout_marginLeft="@dimen/alert_text_margin_border"
+            android:layout_marginRight="@dimen/alert_text_margin_border"
+            android:gravity="center"
+            android:text="Custom Intro is a Custom app and requires the following permissions: "
+            android:textSize="@dimen/alert_text_message_size"
+            android:textColor="@color/alert_text_default"/>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/permissions_list"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/alert_text_message_margin_bottom"
+            android:layout_gravity="center_horizontal"
+            android:scrollbars="none"
+            android:overScrollMode="never"/>
+
+        <Button
+            android:id="@+id/permissions_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="35dp"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:layout_marginTop="18dp"
+            android:layout_marginBottom="18dp"
+            android:layout_marginLeft="12dp"
+            android:layout_marginRight="12dp"
+            android:layout_gravity="center_horizontal"
+            android:text="Grant Permissions"
+            android:textSize="16sp"
+            android:textColor="@color/white"
+            android:fontFamily="sans-serif-medium"
+            android:background="@drawable/icon_add_selector"
+            android:elevation="2dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/permissions_optional"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/alert_button_margin"
+        android:layout_marginBottom="@dimen/alert_button_margin"
+        android:layout_marginLeft="@dimen/alert_dialogue_margin_side"
+        android:layout_marginRight="@dimen/alert_dialogue_margin_side"
+        android:padding="12dp"
+        android:background="@drawable/bg_translucent_white"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/permissions_optional_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/alert_text_margin_border"
+            android:layout_marginRight="@dimen/alert_text_margin_border"
+            android:gravity="center"
+            android:text="Optional Permissions (enable for full app functionality): "
+            android:textSize="@dimen/alert_text_message_size"
+            android:textColor="@color/alert_text_default"/>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/permissions_list_optional"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/alert_text_message_margin_bottom"
+            android:layout_gravity="center_horizontal"
+            android:scrollbars="vertical"
+            android:overScrollMode="never"/>
+    </LinearLayout>
+</LinearLayout>

+ 66 - 0
app/src/main/res/layout/item_permission.xml

@@ -0,0 +1,66 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_marginTop="12dp"
+              android:layout_marginLeft="12dp"
+              android:layout_marginRight="12dp"
+              android:layout_gravity="center_horizontal"
+              android:background="@null"
+              android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/permission_icon"
+        android:layout_width="35dp"
+        android:layout_height="35dp"
+        android:layout_gravity="center_horizontal"
+        android:scaleType="fitCenter"
+        app:srcCompat="@drawable/ic_folder"/>
+
+    <TextView
+        android:id="@+id/permission_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_gravity="center_horizontal"
+        android:text="Permission"
+        android:textSize="14sp"
+        android:textColor="@color/textPrimary"
+        android:maxLines="1"
+        android:singleLine="true"
+        android:ellipsize="end"/>
+
+    <TextView
+        android:id="@+id/permission_detail"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="2dp"
+        android:layout_gravity="center_horizontal"
+        android:text="Permission is required to send app."
+        android:textSize="12sp"
+        android:textColor="@color/textSecondary"/>
+
+    <com.water.example.utils.CustomButton
+        android:id="@+id/permission_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dp"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp"
+        android:paddingLeft="8dp"
+        android:paddingRight="8dp"
+        android:layout_gravity="center_horizontal"
+        android:text="Enable"
+        android:textSize="12sp"
+        android:fontFamily="sans-serif-medium"
+        android:maxLines="1"
+        android:singleLine="true"
+        android:ellipsize="none"
+        app:btn_cornerRadius="20dp"
+        app:btn_strokeWidth="1dp"
+        app:btn_strokeColor="@color/button_pressed"
+        app:btn_unpressColor="@color/transparent"
+        app:btn_pressColor="@color/button_pressed"
+        app:btn_text_unpressColor="@color/button_pressed"
+        app:btn_text_pressColor="@color/white"/>
+</LinearLayout>

TEMPAT SAMPAH
app/src/main/res/mipmap-hdpi/ic_launcher.png


TEMPAT SAMPAH
app/src/main/res/mipmap-ldpi/ic_launcher.png


TEMPAT SAMPAH
app/src/main/res/mipmap-mdpi/ic_launcher.png


TEMPAT SAMPAH
app/src/main/res/mipmap-xhdpi/ic_launcher.png


TEMPAT SAMPAH
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


TEMPAT SAMPAH
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 13 - 0
app/src/main/res/values-w820dp/attrs.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="CustomButton">
+        <attr name="btn_strokeWidth" format="reference|dimension"/>
+        <attr name="btn_strokeColor" format="reference|color"/>
+        <attr name="btn_blur" format="reference|boolean"/>
+        <attr name="btn_cornerRadius" format="reference|dimension"/>
+        <attr name="btn_unpressColor" format="reference|color"/>
+        <attr name="btn_pressColor" format="reference|color"/>
+        <attr name="btn_text_unpressColor" format="reference|color"/>
+        <attr name="btn_text_pressColor" format="reference|color"/>
+    </declare-styleable>
+</resources>

+ 6 - 0
app/src/main/res/values-w820dp/dimens.xml

@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>

+ 33 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">@android:color/holo_red_light</color>
+    <color name="colorPrimaryLight">#ff6666</color>
+    <color name="colorPrimaryDark">@android:color/holo_red_dark</color>
+    <color name="colorAccent">#FF4081</color>
+
+    <!-- Text Colors -->
+    <color name="textPrimary">#2D2D2D</color>
+    <color name="textSecondary">#8C8C8C</color>
+
+    <!-- Button Colors -->
+    <color name="button_normal">#bbbbbb</color>
+    <color name="button_inactive">#757575</color>
+    <color name="button_pressed">@android:color/holo_red_light</color>
+
+    <!-- Basic Colors -->
+    <color name="white">#FFFFFF</color>
+    <color name="white_pressed">#c8ffffff</color>
+    <color name="black">#000000</color>
+    <color name="blackTranslucent">#7c000000</color>
+    <color name="transparent">#00FFFFFF</color>
+    <color name="loading">#40c4ff</color>
+    <color name="green">#15CC87</color>
+    <color name="green_light">#1affa7</color>
+    <color name="red">#FE5442</color>
+    <color name="yellow">#FCD007</color>
+    <color name="gray">#555555</color>
+
+    <!-- Permissions Dialogue Colors -->
+    <color name="alert_actionsheet_header">#c8ffffff</color>
+    <color name="alert_text_default">#000000</color>
+</resources>

+ 23 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,23 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+    <!-- CustomAlertDialogue -->
+    <dimen name="alert_size_divider">1dp</dimen>
+    <dimen name="alert_radius_dialogue">20dp</dimen>
+    <dimen name="alert_radius_actionsheet">10dp</dimen>
+    <dimen name="alert_dialogue_margin_side">30dp</dimen>
+    <dimen name="alert_dialogue_margin_side_land">50dp</dimen>
+    <dimen name="alert_dialogue_margin_top">30dp</dimen>
+    <dimen name="alert_button_height">44dp</dimen>
+    <dimen name="alert_button_text_size">16sp</dimen>
+    <dimen name="alert_button_margin">10dp</dimen>
+    <dimen name="alert_text_margin_border">18dp</dimen>
+    <dimen name="alert_text_margin_bottom">14dp</dimen>
+    <dimen name="alert_text_message_margin_top">12dp</dimen>
+    <dimen name="alert_text_message_margin_bottom">4dp</dimen>
+    <dimen name="alert_text_title_size">20sp</dimen>
+    <dimen name="alert_text_message_size">14sp</dimen>
+    <dimen name="alert_text_title_message_space">6dp</dimen>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">AMRAudioRecorderExample</string>
+</resources>

+ 25 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,25 @@
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <!-- Permissions Dialogue -->
+    <style name="PermissionsDialogue" parent="android:Theme.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:backgroundDimEnabled">true</item>
+        <item name="android:backgroundDimAmount">0.5</item>
+        <item name="android:windowIsFloating">false</item>
+        <item name="android:gravity">center</item>
+    </style>
+    <style name="CustomDialogAnimation">
+        <item name="android:windowEnterAnimation">@anim/fade_in_center</item>
+        <item name="android:windowExitAnimation">@anim/fade_out_center</item>
+    </style>
+</resources>

+ 25 - 0
build.gradle

@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.2.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        google()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 20 - 0
gradle.properties

@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+android.enableJetifier=true
+android.useAndroidX=true

TEMPAT SAMPAH
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Fri Jun 23 12:32:12 CDT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https://mirrors.aliyun.com/gradle/distributions/v4.10.0/gradle-4.10-all.zip

+ 160 - 0
gradlew

@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
settings.gradle

@@ -0,0 +1 @@
+include ':app', ':amraudiorecorder'