├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher_round-web.png │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── samples │ │ │ └── dataprivacy │ │ │ ├── Injection.java │ │ │ ├── model │ │ │ └── Image.java │ │ │ ├── page │ │ │ ├── images │ │ │ │ ├── ImageImporter.java │ │ │ │ ├── ImagesActivity.java │ │ │ │ ├── ImagesContract.java │ │ │ │ ├── ImagesFragment.java │ │ │ │ ├── ImagesPresenter.java │ │ │ │ └── PictureTaker.java │ │ │ ├── importimage │ │ │ │ └── ImageImportActivity.java │ │ │ └── viewimage │ │ │ │ ├── ImageSharer.java │ │ │ │ ├── ImageViewerActivity.java │ │ │ │ ├── ImageViewerContract.java │ │ │ │ ├── ImageViewerFragment.java │ │ │ │ └── ImageViewerPresenter.java │ │ │ ├── storage │ │ │ ├── ImagesRepository.java │ │ │ ├── InMemoryImagesRepository.java │ │ │ └── LocalImagesRepository.java │ │ │ └── util │ │ │ ├── ImageUtils.java │ │ │ ├── ImageViewHolder.java │ │ │ └── ImagesAdapter.java │ └── res │ │ ├── drawable │ │ ├── ic_camera.xml │ │ ├── ic_delete.xml │ │ ├── ic_image.xml │ │ ├── ic_import.xml │ │ └── ic_share.xml │ │ ├── layout │ │ ├── activity_image_import.xml │ │ ├── activity_image_viewer.xml │ │ ├── activity_images.xml │ │ ├── fragment_image_viewer.xml │ │ ├── fragment_images.xml │ │ └── image_card.xml │ │ ├── menu │ │ ├── menu_image_viewer.xml │ │ └── menu_images.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── backup_descriptor.xml │ └── test │ └── java │ └── com │ └── google │ └── samples │ └── dataprivacy │ └── page │ ├── images │ └── ImagesPresenterTest.java │ └── viewimage │ └── ImageViewerPresenterTest.java ├── build.gradle ├── data ├── downloadSampleImages.bat └── downloadSampleImages.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | ### Before you contribute 9 | Before we can use your code, you must sign the 10 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 11 | (CLA), which you can do online. The CLA is necessary mainly because you own the 12 | copyright to your changes, even after your contribution becomes part of our 13 | codebase, so we need your permission to use and distribute your code. We also 14 | need to be sure of various other things—for instance that you'll tell us if you 15 | know that your code infringes on other people's patents. You don't have to sign 16 | the CLA until after you've submitted your code for review and a member has 17 | approved it, but you must do it before we can put your code into our codebase. 18 | Before you start working on a larger contribution, you should get in touch with 19 | us first through the issue tracker with your idea so that we can help out and 20 | possibly guide you. Coordinating up front makes it much easier to avoid 21 | frustration later on. 22 | 23 | ### Code reviews 24 | All submissions, including submissions by project members, require review. We 25 | use Github pull requests for this purpose. 26 | 27 | ### The small print 28 | Contributions made by corporations are covered by a different agreement than 29 | the one above, the 30 | [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 The Android Open Source Project 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This codelab is deprecated. We recommend you to have a look at our [Storage code samples](https://github.com/android/storage-samples) 2 | which reflect a better privacy approach with data. 3 | 4 | # Keep Sensitive Data Safe and Private Codelab 5 | 6 | This repository contains the code for the Android codelab "Keep Sensitive Data 7 | Safe and Private". 8 | 9 | This codelab will cover the correct way to store data securely in an Android 10 | app, how to access data on the device securely and how to limit the amount of 11 | data that apps expose. The codelab will start with a simple app that does not 12 | follow recommended best practices and the developer will fix each API/area in 13 | turn, covering content providers, local storage and directories and permission 14 | access. 15 | 16 | ### Getting started 17 | Follow the instructions on this page to get started. 18 | 19 | Clone this repository and follow the steps in the codelab: 20 | https://codelabs.developers.google.com/codelabs/android-storage-permissions 21 | 22 | ### License 23 | 24 | 25 | ``` 26 | Copyright 2017 Google, Inc. 27 | 28 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 29 | license agreements. See the NOTICE file distributed with this work for 30 | additional information regarding copyright ownership. The ASF licenses this 31 | file to you under the Apache License, Version 2.0 (the "License"); you may not 32 | use this file except in compliance with the License. You may obtain a copy of 33 | the License at 34 | 35 | http://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 39 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 40 | License for the specific language governing permissions and limitations under 41 | the License. 42 | ``` 43 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.android.application' 18 | 19 | android { 20 | compileSdkVersion rootProject.ext.compileSdkVersion 21 | buildToolsVersion rootProject.ext.buildToolsVersion 22 | defaultConfig { 23 | applicationId "com.google.samples.dataprivacy" 24 | minSdkVersion rootProject.ext.minSdkVersion 25 | targetSdkVersion rootProject.ext.targetSdkVersion 26 | vectorDrawables.useSupportLibrary = true 27 | versionCode 1 28 | versionName "1.0" 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion" 41 | implementation "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintLayoutVersion" 42 | implementation "com.android.support:design:$rootProject.ext.supportLibraryVersion" 43 | implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportLibraryVersion" 44 | implementation "com.android.support:support-core-utils:$rootProject.ext.supportLibraryVersion" 45 | 46 | implementation "pub.devrel:easypermissions:$rootProject.ext.easypermissionsVersion" 47 | 48 | androidTestImplementation("com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion", { 49 | exclude group: "com.android.support", module: "support-annotations" 50 | }) 51 | 52 | // Dependencies for local unit tests 53 | testImplementation "junit:junit:$rootProject.ext.junitVersion" 54 | testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" 55 | testImplementation "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion" 56 | testImplementation "org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito" 57 | testImplementation "org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito" 58 | testImplementation "junit:junit:$rootProject.ext.junitVersion" 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can edit the include path and order by changing the proguardFiles 3 | # directive in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # Add any project specific keep options here: 9 | 10 | # If your project uses WebView with JS, uncomment the following 11 | # and specify the fully qualified class name to the JavaScript interface 12 | # class: 13 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 14 | # public *; 15 | #} 16 | 17 | # Uncomment this to preserve the line number information for 18 | # debugging stack traces. 19 | #-keepattributes SourceFile,LineNumberTable 20 | 21 | # If you keep the line number information, uncomment this to 22 | # hide the original source file name. 23 | #-renamesourcefileattribute SourceFile 24 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher_round-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/ic_launcher_round-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/Injection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy; 18 | 19 | 20 | import android.content.Context; 21 | 22 | import com.google.samples.dataprivacy.storage.ImagesRepository; 23 | import com.google.samples.dataprivacy.storage.LocalImagesRepository; 24 | 25 | public abstract class Injection { 26 | 27 | public static ImagesRepository getImageRepository(Context context) { 28 | 29 | return new LocalImagesRepository(context); 30 | // return new InMemoryImageRepository(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/model/Image.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.model; 18 | 19 | 20 | import android.graphics.Bitmap; 21 | 22 | /** 23 | * An image conists of a {@link Bitmap} and a filename. 24 | */ 25 | public class Image { 26 | private Bitmap bitmap; 27 | private String source; 28 | 29 | public Image(String source, Bitmap bitmap) { 30 | this.source = source; 31 | this.bitmap = bitmap; 32 | } 33 | 34 | public Bitmap getImage() { 35 | return bitmap; 36 | } 37 | 38 | public String getSource() { 39 | return source; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/images/ImageImporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.images; 18 | 19 | /** 20 | * Interface that describes the import functionality. Triggers the import function. 21 | */ 22 | public interface ImageImporter { 23 | void importImage(); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/images/ImagesActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.images; 18 | 19 | import android.app.Activity; 20 | import android.content.Intent; 21 | import android.graphics.Bitmap; 22 | import android.os.Bundle; 23 | import android.provider.MediaStore; 24 | import android.support.v4.app.FragmentManager; 25 | import android.support.v4.app.FragmentTransaction; 26 | import android.support.v7.app.AppCompatActivity; 27 | import android.util.Log; 28 | 29 | import com.google.samples.dataprivacy.Injection; 30 | import com.google.samples.dataprivacy.R; 31 | import com.google.samples.dataprivacy.page.importimage.ImageImportActivity; 32 | import com.google.samples.dataprivacy.util.ImageUtils; 33 | 34 | import java.io.File; 35 | 36 | 37 | public class ImagesActivity extends AppCompatActivity implements PictureTaker, ImageImporter { 38 | static final int REQUEST_IMAGE_CAPTURE = 1; 39 | static final int REQUEST_IMAGE_IMPORT = 2; 40 | 41 | private static final String TAG = ImagesActivity.class.getSimpleName(); 42 | 43 | private ImagesContract.Presenter mPresenter; 44 | private ImagesContract.View mView; 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_images); 50 | 51 | ImagesFragment imagesFragment = 52 | (ImagesFragment) getSupportFragmentManager().findFragmentById(R.id.contentframe); 53 | if (imagesFragment == null) { 54 | imagesFragment = new ImagesFragment(); 55 | FragmentManager fragmentManager = getSupportFragmentManager(); 56 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 57 | transaction.add(R.id.contentframe, imagesFragment); 58 | transaction.commit(); 59 | } 60 | 61 | mView = imagesFragment; 62 | mPresenter = new ImagesPresenter(Injection.getImageRepository(this), mView); 63 | } 64 | 65 | @Override 66 | protected void onStart() { 67 | super.onStart(); 68 | mPresenter.setPictureTaker(this); 69 | mPresenter.setImageImporter(this); 70 | } 71 | 72 | @Override 73 | protected void onStop() { 74 | super.onStop(); 75 | mPresenter.clearPictureTaker(); 76 | mPresenter.clearImageImporter(); 77 | } 78 | 79 | @Override 80 | public void takePicture() { 81 | // Fire off a ACTION_IMAGE_CAPTURE intent to launch a camera app. 82 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 83 | if (takePictureIntent.resolveActivity(getPackageManager()) != null) { 84 | startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); 85 | } 86 | } 87 | 88 | @Override 89 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 90 | if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { 91 | // Image capture intent result 92 | onImageCapture(data.getExtras()); 93 | 94 | } else if (requestCode == REQUEST_IMAGE_IMPORT && resultCode == Activity.RESULT_OK) { 95 | // Image import intent result 96 | onImageImport(data.getExtras()); 97 | 98 | } else { 99 | super.onActivityResult(requestCode, resultCode, data); 100 | } 101 | } 102 | 103 | private void onImageImport(Bundle extras) { 104 | String path = extras.getString(ImageImportActivity.IMPORT_RESULT); 105 | if (path != null) { 106 | Log.d(TAG, "Photo successfully imported."); 107 | loadImageFromPath(path); 108 | } 109 | } 110 | 111 | private void onImageCapture(Bundle extras) { 112 | Bitmap imageBitmap = (Bitmap) extras.get("data"); 113 | Log.d(TAG, "Photo successfully taken."); 114 | mPresenter.onPhotoTaken(imageBitmap); 115 | } 116 | 117 | /** 118 | * Load the image referenced by the path. 119 | * 120 | * @param imagePath 121 | */ 122 | private void loadImageFromPath(String imagePath) { 123 | File file = new File(imagePath); 124 | 125 | //TODO - handle file access exceptions, not found etc. 126 | Bitmap b = ImageUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), 200, 200); 127 | 128 | mPresenter.onImportImage(b); 129 | } 130 | 131 | @Override 132 | public void importImage() { 133 | // Start an intent to the ImageImportActivity where the user can select an image. 134 | Intent intent = new Intent(this, ImageImportActivity.class); 135 | startActivityForResult(intent, REQUEST_IMAGE_IMPORT); 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/images/ImagesContract.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.images; 18 | 19 | import android.graphics.Bitmap; 20 | 21 | import com.google.samples.dataprivacy.model.Image; 22 | 23 | import java.util.List; 24 | 25 | /** 26 | * Contract that describes the "images" page that displays a list of all images stored within 27 | * the app. 28 | * It includes options to trigger taking a photo, viewing a photo 29 | * (see {@link com.google.samples.dataprivacy.page.viewimage.ImageViewerContract}) and sharing an 30 | * image. 31 | */ 32 | public interface ImagesContract { 33 | interface View { 34 | void setPresenter(Presenter presenter); 35 | 36 | /** 37 | * Display the list of images on screen. 38 | */ 39 | void showImages(List images); 40 | 41 | /** 42 | * Display a message that no images can be displayed. 43 | */ 44 | void showNoImages(); 45 | 46 | /** 47 | * Open the image viewer page for an image. 48 | */ 49 | void showImage(String Path); 50 | 51 | /** 52 | * Request all necessary permissions required for running the app. 53 | */ 54 | void requestPermissions(); 55 | 56 | /** 57 | * Display an error message explaining that required permissions are missing. 58 | */ 59 | void showMissingPermissions(boolean showMessage); 60 | 61 | } 62 | 63 | interface Presenter { 64 | 65 | /** 66 | * Start operation. Should be called to start the presenter. 67 | */ 68 | void start(); 69 | 70 | /** 71 | * The 'take photo' option has been triggered. Open the take photo screen. 72 | */ 73 | void openTakePhoto(); 74 | 75 | /** 76 | * The 'import photo' option has been triggered. Open the import photo screen. 77 | */ 78 | void openImportPhoto(); 79 | 80 | /** 81 | * An image has been selected to be viewed. Opens the image viwer page. 82 | */ 83 | void openImage(String path); 84 | 85 | /** 86 | * An image has been selected for import. Stores this {@link Bitmap} in the repository. 87 | */ 88 | void onImportImage(Bitmap image); 89 | 90 | /** 91 | * A photo has been taken. Store this {@link Bitmap} in the repository. 92 | */ 93 | void onPhotoTaken(Bitmap image); 94 | 95 | /** 96 | * A result for a permission request has been received. 97 | * @param isGranted True if all required permissions have been granted. False otherwise. 98 | */ 99 | void onPermissionRequestResult(boolean isGranted); 100 | 101 | /** 102 | * Sets the picture taker that can open the take photo screen. 103 | */ 104 | void setPictureTaker(PictureTaker pictureTaker); 105 | 106 | /** 107 | * Clears the photo taker. See {@link #setPictureTaker(PictureTaker)}. 108 | */ 109 | void clearPictureTaker(); 110 | 111 | /** 112 | * Sets the image importer that opens the import image screen. 113 | */ 114 | void setImageImporter(ImageImporter imageImporter); 115 | 116 | /** 117 | * Clears the image importer. See {@link #setImageImporter(ImageImporter)}. 118 | */ 119 | void clearImageImporter(); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/images/ImagesFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.images; 18 | 19 | import android.Manifest; 20 | import android.content.Intent; 21 | import android.os.Bundle; 22 | import android.support.annotation.NonNull; 23 | import android.support.annotation.Nullable; 24 | import android.support.design.widget.BaseTransientBottomBar; 25 | import android.support.design.widget.CoordinatorLayout; 26 | import android.support.design.widget.Snackbar; 27 | import android.support.v4.app.Fragment; 28 | import android.support.v7.widget.RecyclerView; 29 | import android.util.Log; 30 | import android.view.LayoutInflater; 31 | import android.view.Menu; 32 | import android.view.MenuInflater; 33 | import android.view.MenuItem; 34 | import android.view.View; 35 | import android.view.ViewGroup; 36 | 37 | import com.google.samples.dataprivacy.R; 38 | import com.google.samples.dataprivacy.model.Image; 39 | import com.google.samples.dataprivacy.util.ImagesAdapter; 40 | import com.google.samples.dataprivacy.page.viewimage.ImageViewerActivity; 41 | 42 | import java.util.List; 43 | 44 | import pub.devrel.easypermissions.AppSettingsDialog; 45 | import pub.devrel.easypermissions.EasyPermissions; 46 | 47 | public class ImagesFragment extends Fragment implements ImagesContract.View, 48 | EasyPermissions.PermissionCallbacks { 49 | 50 | private static final int REQUEST_STORAGE_PERMISSION = 1; 51 | private static final String TAG = "ImagesFragment"; 52 | 53 | private ImagesContract.Presenter mPresenter; 54 | private ImagesAdapter mImagesAdapter; 55 | 56 | private RecyclerView mRecyclerView; 57 | private View mEmptyView; 58 | private CoordinatorLayout mCoordinatorLayout; 59 | private Snackbar mPermissionSnackbar; 60 | 61 | public ImagesFragment() { 62 | mImagesAdapter = new ImagesAdapter(); 63 | } 64 | 65 | @Nullable 66 | @Override 67 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 68 | @Nullable Bundle savedInstanceState) { 69 | View layout = inflater.inflate(R.layout.fragment_images, null); 70 | layout.findViewById(R.id.fab_camera).setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | mPresenter.openTakePhoto(); 74 | } 75 | }); 76 | 77 | setHasOptionsMenu(true); 78 | 79 | mRecyclerView = (RecyclerView) layout.findViewById(R.id.recycler_view); 80 | mRecyclerView.setAdapter(mImagesAdapter); 81 | mRecyclerView.setHasFixedSize(true); 82 | mImagesAdapter.setOnImageClickListener(new ImagesAdapter.OnImageItemClickListener() { 83 | @Override 84 | public void onImageClick(String path) { 85 | mPresenter.openImage(path); 86 | } 87 | }); 88 | 89 | mCoordinatorLayout = (CoordinatorLayout) layout.findViewById(R.id.images_coordinator_layout); 90 | mEmptyView = layout.findViewById(R.id.empty_view); 91 | 92 | return layout; 93 | } 94 | 95 | @Override 96 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 97 | super.onCreateOptionsMenu(menu, inflater); 98 | inflater.inflate(R.menu.menu_images, menu); 99 | } 100 | 101 | @Override 102 | public boolean onOptionsItemSelected(MenuItem item) { 103 | switch (item.getItemId()) { 104 | case R.id.menu_import: 105 | mPresenter.openImportPhoto(); 106 | return true; 107 | default: 108 | return super.onOptionsItemSelected(item); 109 | } 110 | } 111 | 112 | @Override 113 | public void setPresenter(ImagesContract.Presenter presenter) { 114 | mPresenter = presenter; 115 | } 116 | 117 | @Override 118 | public void onResume() { 119 | super.onResume(); 120 | mPresenter.start(); 121 | } 122 | 123 | @Override 124 | public void showImages(List images) { 125 | mEmptyView.setVisibility(View.GONE); 126 | mRecyclerView.setVisibility(View.VISIBLE); 127 | mImagesAdapter.setImages(images); 128 | } 129 | 130 | @Override 131 | public void showNoImages() { 132 | mEmptyView.setVisibility(View.VISIBLE); 133 | mRecyclerView.setVisibility(View.GONE); 134 | } 135 | 136 | @Override 137 | public void showImage(String path) { 138 | Intent intent = new Intent(getContext(), ImageViewerActivity.class); 139 | intent.putExtra(ImageViewerActivity.ARGUMENT_IMAGE_PATH, path); 140 | startActivity(intent); 141 | 142 | } 143 | 144 | @Override 145 | public void requestPermissions() { 146 | EasyPermissions.requestPermissions(this, 147 | getString(R.string.external_storage_permission_required), 148 | REQUEST_STORAGE_PERMISSION, 149 | Manifest.permission.WRITE_EXTERNAL_STORAGE); 150 | } 151 | 152 | @Override 153 | public void showMissingPermissions(boolean showMessage) { 154 | if (mPermissionSnackbar == null && !showMessage) { 155 | return; 156 | } 157 | 158 | if (mPermissionSnackbar == null) { 159 | mPermissionSnackbar = Snackbar.make(mCoordinatorLayout, R.string.missing_permissions, 160 | BaseTransientBottomBar.LENGTH_INDEFINITE); 161 | mPermissionSnackbar.setAction(R.string.request, new PermissionRequestClickListener()); 162 | } 163 | if (showMessage) { 164 | mPermissionSnackbar.show(); 165 | } else { 166 | mPermissionSnackbar.dismiss(); 167 | } 168 | } 169 | 170 | @Override 171 | public void onPermissionsGranted(int requestCode, List list) { 172 | if (requestCode != REQUEST_STORAGE_PERMISSION) { 173 | return; 174 | } 175 | mPresenter.onPermissionRequestResult(true); 176 | } 177 | 178 | @Override 179 | public void onPermissionsDenied(int requestCode, List list) { 180 | if (requestCode != REQUEST_STORAGE_PERMISSION) { 181 | return; 182 | } 183 | 184 | if (EasyPermissions.somePermissionPermanentlyDenied(this, list)) { 185 | // Permanently denied. Notify presenter. 186 | Log.d(TAG, "Permission has been permanently denied."); 187 | new AppSettingsDialog.Builder(this).build().show(); 188 | } else { 189 | mPresenter.onPermissionRequestResult(false); 190 | } 191 | 192 | } 193 | 194 | @Override 195 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 196 | @NonNull int[] grantResults) { 197 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 198 | 199 | // Forward results to EasyPermissions 200 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 201 | } 202 | 203 | private class PermissionRequestClickListener implements View.OnClickListener { 204 | @Override 205 | public void onClick(View v) { 206 | requestPermissions(); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/images/ImagesPresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.images; 18 | 19 | import android.graphics.Bitmap; 20 | 21 | import com.google.samples.dataprivacy.model.Image; 22 | import com.google.samples.dataprivacy.storage.ImagesRepository; 23 | 24 | import java.util.List; 25 | 26 | public class ImagesPresenter implements ImagesContract.Presenter { 27 | 28 | private ImagesContract.View mView; 29 | private ImagesRepository mImagesRepository; 30 | private PictureTaker mPictureTaker; 31 | private ImageImporter mImageImporter; 32 | 33 | public ImagesPresenter(ImagesRepository imagesRepository, ImagesContract.View imagesView) { 34 | mImagesRepository = imagesRepository; 35 | mView = imagesView; 36 | 37 | mView.setPresenter(this); 38 | } 39 | 40 | @Override 41 | public void start() { 42 | mView.requestPermissions(); 43 | } 44 | 45 | private void showImages() { 46 | List images = mImagesRepository.getImages(); 47 | if (images == null || images.isEmpty()) { 48 | mView.showNoImages(); 49 | } else { 50 | mView.showImages(images); 51 | } 52 | } 53 | 54 | @Override 55 | public void openTakePhoto() { 56 | // mView.showTakePhotoUi(); 57 | if (mPictureTaker == null) { 58 | return; 59 | } 60 | mPictureTaker.takePicture(); 61 | } 62 | 63 | @Override 64 | public void openImportPhoto() { 65 | if (mImageImporter == null) { 66 | return; 67 | } 68 | mImageImporter.importImage(); 69 | } 70 | 71 | @Override 72 | public void openImage(String path) { 73 | mView.showImage(path); 74 | } 75 | 76 | @Override 77 | public void onImportImage(Bitmap image) { 78 | mImagesRepository.saveImage(image); 79 | showImages(); 80 | } 81 | 82 | @Override 83 | public void onPhotoTaken(Bitmap image) { 84 | mImagesRepository.saveImage(image); 85 | showImages(); 86 | } 87 | 88 | 89 | @Override 90 | public void setPictureTaker(PictureTaker pictureTaker) { 91 | mPictureTaker = pictureTaker; 92 | } 93 | 94 | @Override 95 | public void clearPictureTaker() { 96 | mPictureTaker = null; 97 | } 98 | 99 | @Override 100 | public void setImageImporter(ImageImporter imageImporter) { 101 | mImageImporter = imageImporter; 102 | } 103 | 104 | @Override 105 | public void clearImageImporter() { 106 | mImageImporter = null; 107 | } 108 | 109 | @Override 110 | public void onPermissionRequestResult(boolean isGranted) { 111 | if (isGranted) { 112 | mView.showMissingPermissions(false); 113 | showImages(); 114 | } else { 115 | mView.showMissingPermissions(true); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/images/PictureTaker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.images; 18 | 19 | public interface PictureTaker { 20 | void takePicture(); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/importimage/ImageImportActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.importimage; 18 | 19 | import android.Manifest; 20 | import android.app.Activity; 21 | import android.content.Intent; 22 | import android.graphics.Bitmap; 23 | import android.os.Bundle; 24 | import android.os.Environment; 25 | import android.support.annotation.NonNull; 26 | import android.support.design.widget.BaseTransientBottomBar; 27 | import android.support.design.widget.CoordinatorLayout; 28 | import android.support.design.widget.Snackbar; 29 | import android.support.v7.app.AppCompatActivity; 30 | import android.support.v7.widget.RecyclerView; 31 | import android.util.Log; 32 | import android.view.View; 33 | import android.widget.Toast; 34 | 35 | import com.google.samples.dataprivacy.R; 36 | import com.google.samples.dataprivacy.model.Image; 37 | import com.google.samples.dataprivacy.util.ImageUtils; 38 | import com.google.samples.dataprivacy.util.ImagesAdapter; 39 | 40 | import java.io.File; 41 | import java.io.FileFilter; 42 | import java.util.ArrayList; 43 | import java.util.Arrays; 44 | import java.util.LinkedList; 45 | import java.util.List; 46 | 47 | import pub.devrel.easypermissions.AppSettingsDialog; 48 | import pub.devrel.easypermissions.EasyPermissions; 49 | 50 | import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 51 | 52 | public class ImageImportActivity extends AppCompatActivity implements 53 | EasyPermissions.PermissionCallbacks { 54 | public static final String IMPORT_RESULT = 55 | "com.google.samples.dataprivacy.ImageImportActivity.IMPORT_RESULT"; 56 | private static final String TAG = "ImageImportActivity"; 57 | 58 | private static final int MAX_FILES = 25; 59 | private static final int REQUEST_STORAGE_PERMISSION = 1; 60 | 61 | private RecyclerView mRecyclerView; 62 | private View mEmptyView; 63 | private ImagesAdapter mImagesAdapter; 64 | private Snackbar mPermissionSnackbar; 65 | private CoordinatorLayout mCoordinatorLayout; 66 | 67 | private FileFilter mPngFilter = new FileFilter() { 68 | @Override 69 | public boolean accept(File pathname) { 70 | return pathname.isDirectory() || pathname.getName().endsWith("png"); 71 | } 72 | }; 73 | 74 | /* Checks if external storage is available to at least read */ 75 | public static boolean isExternalStorageReadable() { 76 | String state = Environment.getExternalStorageState(); 77 | return Environment.MEDIA_MOUNTED.equals(state) || 78 | Environment.MEDIA_MOUNTED_READ_ONLY.equals(state); 79 | } 80 | 81 | @Override 82 | protected void onCreate(Bundle savedInstanceState) { 83 | super.onCreate(savedInstanceState); 84 | setContentView(R.layout.activity_image_import); 85 | 86 | mEmptyView = findViewById(R.id.empty_view); 87 | mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout); 88 | 89 | mImagesAdapter = new ImagesAdapter(); 90 | mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 91 | mRecyclerView.setAdapter(mImagesAdapter); 92 | mRecyclerView.setHasFixedSize(true); 93 | mImagesAdapter.setOnImageClickListener(new ImagesAdapter.OnImageItemClickListener() { 94 | @Override 95 | public void onImageClick(String path) { 96 | returnImage(path); 97 | } 98 | }); 99 | 100 | showEmptyList(); 101 | 102 | } 103 | 104 | @Override 105 | protected void onResume() { 106 | super.onResume(); 107 | requirePermission(); 108 | } 109 | 110 | private void requirePermission() { 111 | EasyPermissions.requestPermissions 112 | (this, "External storage access is needed to import images.", 113 | REQUEST_STORAGE_PERMISSION, Manifest.permission.READ_EXTERNAL_STORAGE); 114 | } 115 | 116 | 117 | private void returnImage(String path) { 118 | Toast.makeText(this, "Importing image: " + path, Toast.LENGTH_SHORT).show(); 119 | Intent intent = new Intent(); 120 | intent.putExtra(IMPORT_RESULT, path); 121 | setResult(Activity.RESULT_OK, intent); 122 | finish(); 123 | } 124 | 125 | private void loadImages() { 126 | if (!isExternalStorageReadable()) { 127 | finish(); 128 | } 129 | 130 | File externalStorage = android.os.Environment.getExternalStorageDirectory(); 131 | 132 | ArrayList images = new ArrayList<>(MAX_FILES); 133 | LinkedList files = new LinkedList<>(); 134 | files.add(externalStorage); 135 | 136 | 137 | while (!files.isEmpty() && images.size() < MAX_FILES) { 138 | File file = files.pop(); 139 | if (file.isDirectory()) { 140 | // Read the contents of all directories and add them to the list. 141 | files.addAll(Arrays.asList(file.listFiles(mPngFilter))); 142 | } else { 143 | // Read in the file and add it to the list 144 | Bitmap b = ImageUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), 200, 200); 145 | images.add(new Image(file.getAbsolutePath(), b)); 146 | } 147 | } 148 | 149 | showList(images); 150 | 151 | } 152 | 153 | private void showEmptyList() { 154 | mRecyclerView.setVisibility(View.GONE); 155 | mEmptyView.setVisibility(View.VISIBLE); 156 | } 157 | 158 | private void showList(List images) { 159 | if (images == null || images.isEmpty()) { 160 | showEmptyList(); 161 | return; 162 | } 163 | 164 | mImagesAdapter.setImages(images); 165 | mRecyclerView.setVisibility(View.VISIBLE); 166 | mEmptyView.setVisibility(View.GONE); 167 | } 168 | 169 | @Override 170 | public void onPermissionsGranted(int requestCode, List list) { 171 | if (requestCode != REQUEST_STORAGE_PERMISSION || !list.contains(READ_EXTERNAL_STORAGE)) { 172 | return; 173 | } 174 | showPermissionError(false); 175 | loadImages(); 176 | } 177 | 178 | @Override 179 | public void onPermissionsDenied(int requestCode, List list) { 180 | if (requestCode != REQUEST_STORAGE_PERMISSION || !list.contains(READ_EXTERNAL_STORAGE)) { 181 | return; 182 | } 183 | 184 | if (EasyPermissions.somePermissionPermanentlyDenied(this, list)) { 185 | // Permanently denied. Notify presenter. 186 | Log.d(TAG, "Permission has been permanently denied."); 187 | new AppSettingsDialog.Builder(this).build().show(); 188 | } else { 189 | showPermissionError(true); 190 | } 191 | 192 | } 193 | 194 | private void showPermissionError(boolean showMessage) { 195 | if (mPermissionSnackbar == null && !showMessage) { 196 | return; 197 | } 198 | 199 | if (mPermissionSnackbar == null) { 200 | mPermissionSnackbar = Snackbar.make(mCoordinatorLayout, "Missing Permissions", BaseTransientBottomBar.LENGTH_INDEFINITE); 201 | mPermissionSnackbar.setAction("Request", new PermissionRequestClickListener()); 202 | } 203 | if (showMessage) { 204 | mPermissionSnackbar.show(); 205 | } else { 206 | mPermissionSnackbar.dismiss(); 207 | } 208 | } 209 | 210 | @Override 211 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 212 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 213 | 214 | // Forward results to EasyPermissions 215 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 216 | } 217 | 218 | private class PermissionRequestClickListener implements View.OnClickListener { 219 | @Override 220 | public void onClick(View v) { 221 | requirePermission(); 222 | } 223 | } 224 | } -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/viewimage/ImageSharer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.viewimage; 18 | 19 | /** 20 | * Interface that describes the image sharing functionality. 21 | */ 22 | public interface ImageSharer { 23 | /** 24 | * Share an image identified by its absolute path. 25 | * 26 | * @param path The absolute path to the image. 27 | */ 28 | void shareImage(String path); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/viewimage/ImageViewerActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.viewimage; 18 | 19 | import android.content.Intent; 20 | import android.net.Uri; 21 | import android.os.Bundle; 22 | import android.support.v4.app.FragmentManager; 23 | import android.support.v4.app.FragmentTransaction; 24 | import android.support.v4.content.FileProvider; 25 | import android.support.v7.app.AppCompatActivity; 26 | import android.widget.Toast; 27 | 28 | import com.google.samples.dataprivacy.Injection; 29 | import com.google.samples.dataprivacy.R; 30 | 31 | import java.io.File; 32 | 33 | public class ImageViewerActivity extends AppCompatActivity implements ImageSharer { 34 | 35 | public static final String ARGUMENT_IMAGE_PATH = "ARGUMENT_IMAGE_PATH"; 36 | private ImageViewerFragment mFragment; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_image_viewer); 42 | 43 | ImageViewerFragment fragment = (ImageViewerFragment) getSupportFragmentManager().findFragmentById(R.id.contentframe); 44 | String image = getIntent().getStringExtra(ARGUMENT_IMAGE_PATH); 45 | if (fragment == null) { 46 | 47 | fragment = ImageViewerFragment.newInstance(); 48 | 49 | FragmentManager fragmentManager = getSupportFragmentManager(); 50 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 51 | transaction.add(R.id.contentframe, fragment); 52 | transaction.commit(); 53 | } 54 | 55 | new ImageViewerPresenter(fragment, Injection.getImageRepository(this), this, image); 56 | 57 | } 58 | 59 | /** 60 | * Share an image identified by its absolute path. 61 | * 62 | * @param path The absolute path to the image. 63 | */ 64 | @Override 65 | public void shareImage(String path) { 66 | Toast.makeText(this, "TODO: implement sharing.", Toast.LENGTH_SHORT).show(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/viewimage/ImageViewerContract.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.viewimage; 18 | 19 | 20 | import android.graphics.Bitmap; 21 | 22 | /** 23 | * Contract for the "image viwer" page that displays an image. It includes operations that can 24 | * be made on the loaded image, such as deleting or sharing it. 25 | */ 26 | public interface ImageViewerContract { 27 | 28 | interface View { 29 | void setPresenter(Presenter presenter); 30 | 31 | void displayImage(Bitmap image); 32 | 33 | void displayImageDeleted(); 34 | 35 | } 36 | 37 | interface Presenter { 38 | 39 | void start(); 40 | 41 | void showImage(); 42 | 43 | void deleteImage(); 44 | 45 | void shareImage(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/viewimage/ImageViewerFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.viewimage; 18 | 19 | import android.graphics.Bitmap; 20 | import android.os.Bundle; 21 | import android.support.v4.app.Fragment; 22 | import android.support.v7.app.AppCompatActivity; 23 | import android.support.v7.widget.Toolbar; 24 | import android.view.LayoutInflater; 25 | import android.view.Menu; 26 | import android.view.MenuInflater; 27 | import android.view.MenuItem; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.ImageView; 31 | 32 | import com.google.samples.dataprivacy.R; 33 | 34 | /** 35 | * A fragment displaying an image. See {@link ImageViewerContract}. 36 | * Delete and share functions are exposed through toolbar items. 37 | */ 38 | public class ImageViewerFragment extends Fragment implements ImageViewerContract.View { 39 | 40 | private ImageView mImageView; 41 | private ImageViewerContract.Presenter mPresenter; 42 | 43 | public static ImageViewerFragment newInstance() { 44 | return new ImageViewerFragment(); 45 | } 46 | 47 | @Override 48 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 49 | Bundle savedInstanceState) { 50 | View layout = inflater.inflate(R.layout.fragment_image_viewer, container, false); 51 | mImageView = (ImageView) layout.findViewById(R.id.image_view); 52 | 53 | setHasOptionsMenu(true); 54 | Toolbar toolbar = (Toolbar) layout.findViewById(R.id.toolbar); 55 | ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); 56 | 57 | return layout; 58 | } 59 | 60 | @Override 61 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 62 | inflater.inflate(R.menu.menu_image_viewer, menu); 63 | } 64 | 65 | @Override 66 | public boolean onOptionsItemSelected(MenuItem item) { 67 | switch (item.getItemId()) { 68 | case R.id.action_delete: 69 | mPresenter.deleteImage(); 70 | return true; 71 | case R.id.action_share: 72 | mPresenter.shareImage(); 73 | return true; 74 | default: 75 | return super.onOptionsItemSelected(item); 76 | } 77 | } 78 | 79 | @Override 80 | public void onResume() { 81 | super.onResume(); 82 | mPresenter.start(); 83 | } 84 | 85 | @Override 86 | public void setPresenter(ImageViewerContract.Presenter presenter) { 87 | mPresenter = presenter; 88 | } 89 | 90 | @Override 91 | public void displayImage(Bitmap image) { 92 | mImageView.setImageBitmap(image); 93 | } 94 | 95 | @Override 96 | public void displayImageDeleted() { 97 | getActivity().finish(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/page/viewimage/ImageViewerPresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.page.viewimage; 18 | 19 | 20 | import android.graphics.Bitmap; 21 | import android.support.annotation.NonNull; 22 | 23 | import com.google.samples.dataprivacy.storage.ImagesRepository; 24 | 25 | public class ImageViewerPresenter implements ImageViewerContract.Presenter { 26 | 27 | private final String mImage; 28 | private ImageViewerContract.View mView; 29 | private ImagesRepository mImagesRepository; 30 | private ImageSharer mImageSharer; 31 | 32 | 33 | public ImageViewerPresenter(@NonNull ImageViewerContract.View view, @NonNull ImagesRepository repository, @NonNull ImageSharer imageSharer, @NonNull String image) { 34 | this.mView = view; 35 | this.mImagesRepository = repository; 36 | this.mImageSharer = imageSharer; 37 | this.mImage = image; 38 | 39 | view.setPresenter(this); 40 | } 41 | 42 | @Override 43 | public void start() { 44 | showImage(); 45 | } 46 | 47 | @Override 48 | public void showImage() { 49 | Bitmap bitmap = mImagesRepository.getImage(mImage); 50 | mView.displayImage(bitmap); 51 | } 52 | 53 | @Override 54 | public void deleteImage() { 55 | mImagesRepository.deleteImage(mImage); 56 | mView.displayImageDeleted(); 57 | } 58 | 59 | @Override 60 | public void shareImage() { 61 | mImageSharer.shareImage(mImage); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/storage/ImagesRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.storage; 18 | 19 | 20 | import android.graphics.Bitmap; 21 | 22 | import com.google.samples.dataprivacy.model.Image; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Contract for the data store of images. 28 | */ 29 | public interface ImagesRepository { 30 | String saveImage(Bitmap image); 31 | 32 | void deleteImage(String path); 33 | 34 | List getImages(); 35 | 36 | Bitmap getImage(String path); 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/storage/InMemoryImagesRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.storage; 18 | 19 | 20 | import android.graphics.Bitmap; 21 | 22 | import com.google.samples.dataprivacy.model.Image; 23 | 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.UUID; 30 | 31 | /** 32 | * In Memory implementation of an ImagesRepository that stores 33 | * all data only in memory. 34 | */ 35 | public class InMemoryImagesRepository implements ImagesRepository { 36 | 37 | private HashMap mImageMap = new HashMap<>(); 38 | 39 | @Override 40 | public String saveImage(Bitmap image) { 41 | final String key = UUID.randomUUID().toString() + ".png"; 42 | mImageMap.put(key, image); 43 | 44 | return key; 45 | } 46 | 47 | @Override 48 | public void deleteImage(String path) { 49 | mImageMap.remove(path); 50 | } 51 | 52 | @Override 53 | public List getImages() { 54 | Iterator> iterator = mImageMap.entrySet().iterator(); 55 | ArrayList images = new ArrayList<>(mImageMap.size()); 56 | while (iterator.hasNext()) { 57 | Map.Entry entry = iterator.next(); 58 | images.add(new Image(entry.getKey(), entry.getValue())); 59 | } 60 | 61 | return images; 62 | } 63 | 64 | @Override 65 | public Bitmap getImage(String path) { 66 | return mImageMap.get(path); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/storage/LocalImagesRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.storage; 18 | 19 | import android.content.Context; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.os.Environment; 23 | import android.util.Log; 24 | 25 | import com.google.samples.dataprivacy.model.Image; 26 | 27 | import java.io.File; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | import java.util.UUID; 33 | 34 | /** 35 | * Repository of images that stores data on the device. The initial implementation is using 36 | * the external storage directory (see {@link Environment#getExternalStorageDirectory()}. 37 | */ 38 | public class LocalImagesRepository implements ImagesRepository { 39 | 40 | private static final String TAG = "LocalImagesRepository"; 41 | private static final String PATH = "secureimages/"; 42 | 43 | private File mStorage; 44 | 45 | public LocalImagesRepository(Context context) { 46 | File externalStorage = Environment.getExternalStorageDirectory(); 47 | mStorage = new File(externalStorage, PATH); 48 | 49 | if (!mStorage.exists()) { 50 | if (!mStorage.mkdirs()) { 51 | Log.e(TAG, "Could not create storage directory: " + mStorage.getAbsolutePath()); 52 | 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * Generates a file name for the png image and stores it in local storage. 59 | * 60 | * @param image The bitmap to store. 61 | * @return The name of the image file. 62 | */ 63 | @Override 64 | public String saveImage(Bitmap image) { 65 | final String fileName = UUID.randomUUID().toString() + ".png"; 66 | File file = new File(mStorage, fileName); 67 | 68 | try (FileOutputStream fos = new FileOutputStream(file)) { 69 | image.compress(Bitmap.CompressFormat.PNG, 85, fos); 70 | } catch (IOException e) { 71 | Log.e(TAG, "Error during saving of image: " + e.getMessage()); 72 | return null; 73 | } 74 | 75 | return fileName; 76 | } 77 | 78 | /** 79 | * Deletes the given image. 80 | * 81 | * @param fileName Filename of the image to delete. 82 | */ 83 | @Override 84 | public void deleteImage(String fileName) { 85 | File file = new File(fileName); 86 | if (!file.delete()) { 87 | Log.e(TAG, "File could not be deleted: " + fileName); 88 | } 89 | } 90 | 91 | /** 92 | * Returns a list of all images stored in this repository. 93 | * An {@link Image} contains a {@link Bitmap} and a string with its filename. 94 | * 95 | * @return 96 | */ 97 | @Override 98 | public List getImages() { 99 | File[] files = mStorage.listFiles(); 100 | if (files == null) { 101 | Log.e(TAG, "Could not list files."); 102 | return null; 103 | } 104 | ArrayList list = new ArrayList<>(files.length); 105 | for (File f : files) { 106 | Bitmap bitmap = BitmapFactory.decodeFile(f.getAbsolutePath()); 107 | list.add(new Image(f.getAbsolutePath(), bitmap)); 108 | } 109 | return list; 110 | } 111 | 112 | /** 113 | * Loads the given file as a bitmap. 114 | */ 115 | @Override 116 | public Bitmap getImage(String path) { 117 | File file = new File(path); 118 | if (!file.exists()) { 119 | Log.e(TAG, "File could not opened. It does not exist: " + path); 120 | 121 | return null; 122 | } 123 | 124 | return BitmapFactory.decodeFile(file.getAbsolutePath()); 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/util/ImageUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.util; 18 | 19 | import android.graphics.Bitmap; 20 | import android.graphics.BitmapFactory; 21 | 22 | /** 23 | * Various handy image utility methods. 24 | */ 25 | public class ImageUtils { 26 | 27 | /** 28 | * Calculates the sample size to load an image, described by its 29 | * {@link android.graphics.BitmapFactory.Options} 30 | * into a given width and height. 31 | *

32 | * Source: https://developer.android.com/topic/performance/graphics/load-bitmap.html 33 | */ 34 | public static int calculateInSampleSize( 35 | BitmapFactory.Options options, int reqWidth, int reqHeight) { 36 | // Raw height and width of image 37 | final int height = options.outHeight; 38 | final int width = options.outWidth; 39 | int inSampleSize = 1; 40 | 41 | if (height > reqHeight || width > reqWidth) { 42 | 43 | final int halfHeight = height / 2; 44 | final int halfWidth = width / 2; 45 | 46 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 47 | // height and width larger than the requested height and width. 48 | while ((halfHeight / inSampleSize) >= reqHeight 49 | && (halfWidth / inSampleSize) >= reqWidth) { 50 | inSampleSize *= 2; 51 | } 52 | } 53 | 54 | return inSampleSize; 55 | } 56 | 57 | /** 58 | * Loads an image with a maximum width and height. 59 | *

60 | * Source based on: https://developer.android.com/topic/performance/graphics/load-bitmap.html 61 | */ 62 | public static Bitmap decodeSampledBitmapFromFile(String file, 63 | int reqWidth, int reqHeight) { 64 | 65 | // First decode with inJustDecodeBounds=true to check dimensions 66 | final BitmapFactory.Options options = new BitmapFactory.Options(); 67 | options.inJustDecodeBounds = true; 68 | BitmapFactory.decodeFile(file, options); 69 | 70 | // Calculate inSampleSize 71 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 72 | 73 | // Decode bitmap with inSampleSize set 74 | options.inJustDecodeBounds = false; 75 | return BitmapFactory.decodeFile(file, options); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/util/ImageViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.util; 18 | 19 | import android.support.annotation.NonNull; 20 | import android.support.v7.widget.RecyclerView; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.ImageView; 25 | 26 | import com.google.samples.dataprivacy.R; 27 | import com.google.samples.dataprivacy.model.Image; 28 | 29 | 30 | public class ImageViewHolder extends RecyclerView.ViewHolder { 31 | 32 | protected ImageView imageView; 33 | 34 | public ImageViewHolder(View itemView) { 35 | super(itemView); 36 | imageView = (ImageView) itemView.findViewById(R.id.image); 37 | } 38 | 39 | public static ImageViewHolder newInstance(@NonNull ViewGroup parent) { 40 | return new ImageViewHolder(LayoutInflater.from(parent.getContext()) 41 | .inflate(R.layout.image_card, parent, false)); 42 | } 43 | 44 | public void bind(Image image) { 45 | 46 | imageView.setImageBitmap(image.getImage()); 47 | imageView.setTag(image.getSource()); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/samples/dataprivacy/util/ImagesAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.dataprivacy.util; 18 | 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.widget.TextView; 23 | 24 | import com.google.samples.dataprivacy.model.Image; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * A RecyclerView adapter that displays {@link Image}s. Includes a callback for when an item has 31 | * been clicked. 32 | */ 33 | public class ImagesAdapter extends RecyclerView.Adapter { 34 | private List mImages = new ArrayList<>(0); 35 | private OnImageItemClickListener mListener; 36 | 37 | public ImagesAdapter() { 38 | } 39 | 40 | public void setImages(List images) { 41 | mImages = images; 42 | notifyDataSetChanged(); 43 | } 44 | 45 | public void setOnImageClickListener(OnImageItemClickListener listener) { 46 | mListener = listener; 47 | } 48 | 49 | @Override 50 | public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 51 | return ImageViewHolder.newInstance(parent); 52 | } 53 | 54 | @Override 55 | public void onBindViewHolder(ImageViewHolder holder, final int position) { 56 | holder.bind(mImages.get(position)); 57 | holder.imageView.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | if (mListener == null) { 61 | return; 62 | } 63 | mListener.onImageClick(mImages.get(position).getSource()); 64 | } 65 | }); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return mImages.size(); 71 | 72 | 73 | } 74 | 75 | public interface OnImageItemClickListener { 76 | void onImageClick(String path); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_image.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_import.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_image_import.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 28 | 29 | 37 | 38 | 45 | 46 | 52 | 53 | 54 | 55 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_image_viewer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_images.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_image_viewer.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 29 | 30 | 36 | 37 | 38 | 39 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_images.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 25 | 26 | 30 | 31 | 32 | 50 | 51 | 52 | 66 | 67 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_card.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_image_viewer.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 |

21 | 22 | 28 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_images.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/android-storage-permissions/94fc937dbeb84dfa4abde416a24095d3f21c3313/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | #673AB7 19 | #512DA8 20 | #D1C4E9 21 | #FFC107 22 | #BDBDBD 23 | 24 | #AAAAAA 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 16dp 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | Data Privacy 19 | Image Viewer 20 | Settings 21 | Import Image 22 | selected image 23 | Share 24 | Delete 25 | This app requires reading storage from external 26 | directories in order to access images from other sources. 27 | Welcome to the data privacy codelab.\nThis application stores 28 | images \'securely\'. Take a photo or import an image to store it within the app. 29 | No images saved yet.\nTake a photo or import an image. 30 | No images available for import. 31 | External storage access is need to store images. 32 | Missing Permissions 33 | Request 34 | No image icon 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 31 | 32 |