├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── rikka │ │ └── searchbyimage │ │ └── ApplicationTest.java │ ├── google │ └── res │ │ └── xml │ │ └── shortcuts.xml │ ├── main │ ├── AndroidManifest.xml │ ├── aidl │ │ └── com │ │ │ └── android │ │ │ └── vending │ │ │ └── billing │ │ │ └── IInAppBillingService.aidl │ ├── assets │ │ └── saucenao-new.css │ ├── java │ │ ├── com │ │ │ └── android │ │ │ │ └── vending │ │ │ │ └── billing │ │ │ │ ├── IabBroadcastReceiver.java │ │ │ │ ├── IabException.java │ │ │ │ ├── IabHelper.java │ │ │ │ ├── IabResult.java │ │ │ │ ├── Inventory.java │ │ │ │ ├── Purchase.java │ │ │ │ ├── Security.java │ │ │ │ └── SkuDetails.java │ │ └── rikka │ │ │ └── searchbyimage │ │ │ ├── Application.java │ │ │ ├── apdater │ │ │ ├── FilterAdapterHelper.java │ │ │ ├── PostFormAdapter.java │ │ │ ├── ResultAdapter.java │ │ │ ├── SearchEngineAdapter.java │ │ │ └── SimpleAdapter.java │ │ │ ├── database │ │ │ ├── DatabaseHelper.java │ │ │ └── table │ │ │ │ └── CustomEngineTable.java │ │ │ ├── receiver │ │ │ ├── ShareBroadcastReceiver.java │ │ │ └── UploadMessageReceiver.java │ │ │ ├── service │ │ │ ├── UploadParam.java │ │ │ ├── UploadResult.java │ │ │ └── UploadService.java │ │ │ ├── staticdata │ │ │ ├── EngineId.java │ │ │ ├── SearchEngine.java │ │ │ ├── SearchEngineParcelable.java │ │ │ └── StaticData.java │ │ │ ├── support │ │ │ ├── CrashHandler.java │ │ │ ├── GetDeviceInfo.java │ │ │ ├── OkHttpClientProvider.java │ │ │ └── Settings.java │ │ │ ├── ui │ │ │ ├── BaseActivity.java │ │ │ ├── BaseResultActivity.java │ │ │ ├── ChromeCustomTabsActivity.java │ │ │ ├── DonateActivity.java │ │ │ ├── EditSiteInfoActivity.java │ │ │ ├── EditSitesActivity.java │ │ │ ├── IqdbResultActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── ResultActivity.java │ │ │ ├── SendReportActivity.java │ │ │ ├── SettingsFragment.java │ │ │ ├── UploadActivity.java │ │ │ └── WebViewActivity.java │ │ │ ├── utils │ │ │ ├── ArrayUtils.java │ │ │ ├── BrowsersUtils.java │ │ │ ├── ClipBoardUtils.java │ │ │ ├── CustomTabActivityHelper.java │ │ │ ├── CustomTabsHelper.java │ │ │ ├── DatabindingHelper.java │ │ │ ├── DownloadManagerResolver.java │ │ │ ├── FilenameResolver.java │ │ │ ├── HttpUtils.java │ │ │ ├── IabHelperWrapper.java │ │ │ ├── ImageUtils.java │ │ │ ├── IntentUtils.java │ │ │ ├── IqdbResultCollecter.java │ │ │ ├── PackageUtils.java │ │ │ ├── ParcelableUtils.java │ │ │ ├── UploadResultUtils.java │ │ │ └── Utils.java │ │ │ ├── view │ │ │ ├── ContextMenuTitleView.java │ │ │ ├── InfoBarLayout.java │ │ │ └── WebViewToolBar.java │ │ │ ├── viewholder │ │ │ └── ListBottomSheetItemViewHolder.java │ │ │ └── widget │ │ │ ├── DropDown.java │ │ │ ├── FABAwareScrollingViewBehavior.java │ │ │ ├── InfoBar.java │ │ │ ├── ListBottomSheetDialog.java │ │ │ └── MyLinearLayoutManager.java │ └── res │ │ ├── anim │ │ ├── fade_in.xml │ │ └── fade_out.xml │ │ ├── animator │ │ ├── press_hide.xml │ │ └── raise.xml │ │ ├── color-night │ │ └── primary_text.xml │ │ ├── color │ │ └── primary_text.xml │ │ ├── drawable-hdpi │ │ ├── ic_stat.png │ │ └── ic_stat_cancel.png │ │ ├── drawable-mdpi │ │ ├── ic_stat.png │ │ └── ic_stat_cancel.png │ │ ├── drawable-xhdpi │ │ ├── ic_stat.png │ │ └── ic_stat_cancel.png │ │ ├── drawable-xxhdpi │ │ ├── ic_stat.png │ │ └── ic_stat_cancel.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_stat.png │ │ └── ic_stat_cancel.png │ │ ├── drawable │ │ ├── check.xml │ │ ├── close.xml │ │ ├── cloud_upload.xml │ │ ├── ic_add_24dp.xml │ │ ├── ic_all_out_24dp.xml │ │ ├── ic_backup_24dp.xml │ │ ├── ic_check_24dp.xml │ │ ├── ic_clear_24dp.xml │ │ ├── ic_cloud_24dp.xml │ │ ├── ic_cloud_upload_24dp.xml │ │ ├── ic_crop_24dp.xml │ │ ├── ic_error_24dp.xml │ │ ├── ic_favorite_24dp.xml │ │ ├── ic_file_upload_24dp.xml │ │ ├── ic_format_list_bulleted_24dp.xml │ │ ├── ic_get_app_black_24dp.xml │ │ ├── ic_help_24dp.xml │ │ ├── ic_icon_google_24dp.xml │ │ ├── ic_icon_other_24dp.xml │ │ ├── ic_insert_link_24dp.xml │ │ ├── ic_logo_alipay.xml │ │ ├── ic_logo_play.xml │ │ ├── ic_mode_edit_24dp.xml │ │ ├── ic_more_vert_not_press_24dp.xml │ │ ├── ic_more_vert_pressed_24dp.xml │ │ ├── ic_more_vert_selector.xml │ │ ├── ic_open_in_browser_24dp.xml │ │ ├── ic_open_in_new_24dp.xml │ │ ├── ic_qu_upload.xml │ │ ├── ic_security_24dp.xml │ │ ├── ic_settings_24dp.xml │ │ ├── ic_web_24dp.xml │ │ ├── infobar_close.png │ │ ├── infobar_download.png │ │ ├── line_divider_search_engine_list.xml │ │ └── plus.xml │ │ ├── layout │ │ ├── activity_add_engine.xml │ │ ├── activity_donate.xml │ │ ├── activity_edit_sites.xml │ │ ├── activity_main.xml │ │ ├── activity_result.xml │ │ ├── activity_upload.xml │ │ ├── activity_webview.xml │ │ ├── bottom_sheet_list_ltem.xml │ │ ├── drop_down.xml │ │ ├── infobar.xml │ │ ├── list_item_edit_sites.xml │ │ ├── list_item_edit_sites_empty.xml │ │ ├── list_item_edit_sites_header.xml │ │ ├── list_item_iqdb_result.xml │ │ ├── list_item_post_form.xml │ │ └── list_item_post_form_add.xml │ │ ├── menu │ │ ├── edit_info.xml │ │ ├── main.xml │ │ └── webview.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── styles.xml │ │ ├── values-sw600dp │ │ └── dimens.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rHK │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values │ │ ├── array.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── backup_scheme.xml │ │ ├── file_paths.xml │ │ ├── preferences_about.xml │ │ ├── preferences_general.xml │ │ ├── preferences_search_settings.xml │ │ ├── preferences_usage.xml │ │ └── shortcuts.xml │ └── test │ └── java │ └── rikka │ └── searchbyimage │ └── ExampleUnitTest.java ├── build.gradle ├── cropper ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── theartofdev │ │ └── edmodo │ │ └── cropper │ │ ├── BitmapCroppingWorkerTask.java │ │ ├── BitmapLoadingWorkerTask.java │ │ ├── BitmapUtils.java │ │ ├── CropImage.java │ │ ├── CropImageActivity.java │ │ ├── CropImageAnimation.java │ │ ├── CropImageOptions.java │ │ ├── CropImageView.java │ │ ├── CropOverlayView.java │ │ ├── CropWindowHandler.java │ │ └── CropWindowMoveHandler.java │ └── res │ ├── drawable-hdpi │ ├── crop_image_menu_rotate_left.png │ └── crop_image_menu_rotate_right.png │ ├── drawable-xhdpi │ ├── crop_image_menu_rotate_left.png │ └── crop_image_menu_rotate_right.png │ ├── drawable-xxhdpi │ ├── crop_image_menu_rotate_left.png │ └── crop_image_menu_rotate_right.png │ ├── drawable-xxxhdpi │ ├── crop_image_menu_rotate_left.png │ └── crop_image_menu_rotate_right.png │ ├── layout │ ├── crop_image_activity.xml │ └── crop_image_view.xml │ ├── menu │ └── crop_image_menu.xml │ └── values │ ├── attrs.xml │ ├── ids.xml │ └── strings.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | *.apk 10 | .idea/ 11 | gradle.properties 12 | google-services.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SearchByImage 2 | =============== 3 | 4 | ![](./app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) 5 | 6 | Upload image file to Google / Baidu / iqdb / TinEye / SauceNAO. 7 | 8 | Download from [Play Store](https://play.google.com/store/apps/details?id=rikka.searchbyimage2) (Google Play edition can only use Google) 9 | 10 | Download from [CoolApk](http://coolapk.com/apk/rikka.searchbyimage) (Coolapk edition can use all services) 11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .apk 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | dataBinding { 8 | enabled = true 9 | } 10 | 11 | defaultConfig { 12 | minSdkVersion 15 13 | targetSdkVersion 25 14 | versionCode 20200 15 | 16 | vectorDrawables.useSupportLibrary = true 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | productFlavors { 27 | def String name = "2.0.1" 28 | 29 | coolapk { 30 | applicationId "rikka.searchbyimage" 31 | versionName name 32 | buildConfigField "boolean", "hideOtherEngine", "false" 33 | } 34 | 35 | google { 36 | applicationId "rikka.searchbyimage2" 37 | versionName name + "-play" 38 | buildConfigField "boolean", "hideOtherEngine", "true" 39 | } 40 | } 41 | 42 | applicationVariants.all { variant -> 43 | variant.outputs.each { output -> 44 | if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) { 45 | output.outputFile = file("${output.outputFile.parent}/searchbyimage" + 46 | "-${variant.buildType.name.toLowerCase()}" + 47 | "-${variant.versionName}" + 48 | ".apk") 49 | } 50 | } 51 | } 52 | } 53 | 54 | dependencies { 55 | compile fileTree(dir: 'libs', include: ['*.jar']) 56 | testCompile 'junit:junit:4.12' 57 | compile 'com.android.support:appcompat-v7:25.1.0' 58 | compile "com.android.support:customtabs:25.1.0" 59 | compile 'com.android.support:design:25.1.0' 60 | compile 'com.android.support:cardview-v7:25.1.0' 61 | compile 'com.github.bumptech.glide:glide:3.7.0' 62 | compile 'com.squareup.okhttp3:okhttp:3.5.0' 63 | compile 'io.reactivex:rxjava:1.2.3' 64 | compile 'io.reactivex:rxandroid:1.2.1' 65 | compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.1@aar' 66 | compile 'com.github.RikkaW:MaterialPreference:v1.4.4' 67 | compile project(':cropper') 68 | 69 | compile 'com.google.firebase:firebase-core:10.0.1' 70 | compile 'com.google.firebase:firebase-crash:10.0.1' 71 | } 72 | 73 | apply plugin: 'com.google.gms.google-services' 74 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Rikka\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/rikka/searchbyimage/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/google/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/assets/saucenao-new.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #1D1D1D; 3 | margin: 0px; 4 | padding: 0px; 5 | } 6 | body,td,th { 7 | color: #eeeeee; 8 | } 9 | #footerarea, #headerarea, #message, #left, #randomMessage, #smalllogo, #Search { 10 | display: none; !important 11 | } 12 | a:link { 13 | text-decoration: none; 14 | color: #cccccc; 15 | } 16 | a:visited { 17 | text-decoration: none; 18 | color: #aaaaaa; 19 | } 20 | a:hover { 21 | text-decoration: none; 22 | color: #555555; 23 | } 24 | a:active { 25 | text-decoration: none; 26 | } 27 | li { 28 | display:inline; 29 | list-style-type:none; 30 | padding-left:1em; 31 | margin-left:1em; 32 | border-left:1px solid #333333; 33 | } 34 | li:first-child { 35 | border-left:none; 36 | } 37 | 38 | #mainarea{ 39 | margin-top:5px; 40 | } 41 | #mainarea-expander{ 42 | clear:both; 43 | } 44 | #headerbar{ 45 | display:none; 46 | } 47 | #headerbarleft{ 48 | float: left; 49 | text-align: left; 50 | margin-left:1px; 51 | width:0px; 52 | overflow:visible; 53 | } 54 | #headerbarright{ 55 | float: right; 56 | text-align: right; 57 | margin-right:1px; 58 | width:0px; 59 | overflow:visible; 60 | direction: rtl; 61 | } 62 | #headerbarmiddle{ 63 | text-align:center; 64 | } 65 | #yourimagecontainer { 66 | vertical-align:top; 67 | padding-top:11px; 68 | } 69 | #yourimage { 70 | margin-left:1px; 71 | float:right; 72 | vertical-align:top; 73 | } 74 | #yourimagetext { 75 | vertical-align:top; 76 | } 77 | #yourimageretrylinks { 78 | padding-top:1px; 79 | clear:both; 80 | text-align:right; 81 | } 82 | #smalllogo { 83 | margin-bottom:1px; 84 | text-align:left; 85 | } 86 | #right{ 87 | float: right; 88 | width: 50px; 89 | text-align: left; 90 | } 91 | #middle{ 92 | text-align:right; 93 | } 94 | #footer-left{ 95 | float: left; 96 | width: 175px; 97 | text-align: right; 98 | } 99 | #footer-right{ 100 | float: right; 101 | width: 50px; 102 | text-align: left; 103 | } 104 | #footer-middle{ 105 | margin-left: 180px; 106 | margin-right: 50px; 107 | text-align:center; 108 | } 109 | .result{ 110 | background-color:#2d2d2d; 111 | margin-bottom:5px; 112 | color:#FFF; 113 | border:1px solid #252525; 114 | text-align:left;/*for the 'no results' message*/ 115 | } 116 | .hidden{ 117 | display: none; 118 | } 119 | .resulttable{ 120 | width: 100%; 121 | border-width:0px; 122 | border-collapse:collapse; 123 | } 124 | .resulttableimage{ 125 | background-color:#272727; 126 | vertical-align:top; 127 | padding:0px; 128 | } 129 | .resulttablecontent{ 130 | width: 100%; 131 | vertical-align:top; 132 | padding:0px; 133 | } 134 | .resultcontent{ 135 | text-align:left; 136 | padding-left:5px; 137 | clear:left; 138 | } 139 | .resultimage{ 140 | float:left; 141 | text-align:left; 142 | vertical-align: top; 143 | min-width:108px; 144 | } 145 | .resultimage img{ 146 | max-width: 108px; 147 | max-height: 108px; 148 | } 149 | .resultmatchinfo{ 150 | float:right; 151 | vertical-align:top; 152 | margin-right:2px; 153 | } 154 | .resultdebuginfo{ 155 | font-size: 9px; 156 | color: #999999; 157 | float:left; 158 | vertical-align:top; 159 | } 160 | .resultsimilarityinfo{ 161 | font-size: 10px; 162 | color: #999999; 163 | text-align:right; 164 | vertical-align:top; 165 | float:right; 166 | } 167 | .resultmiscinfo{ 168 | text-align:right; 169 | vertical-align:top; 170 | float:right; 171 | clear:right; 172 | } 173 | .resulttitle{ 174 | margin-bottom:5px; 175 | } 176 | .resultcontentcolumn{ 177 | float:left; 178 | margin-right:50px; 179 | } 180 | .servererror { 181 | font-size: 10px; 182 | background-color: #430000; 183 | border-bottom: 1px solid #310000; 184 | color:#999; 185 | text-align:center; 186 | } 187 | #warnmessage { 188 | text-align:center; 189 | color:#aaa; 190 | background-color: #430000; 191 | border-bottom: 1px solid #310000; 192 | } 193 | .linkify{ 194 | color: #eeeeee; 195 | } 196 | .subtext { 197 | font-size: 9px; 198 | } 199 | .alternatetext { 200 | font-size: 11px; 201 | } 202 | .descriptor { 203 | font-size: 9px; 204 | color: #999999; 205 | } 206 | #settingsmenu{ 207 | margin-right: 20px; 208 | font-family:Georgia, "Times New Roman", Times, serif; 209 | } 210 | .settingsmenuheader{ 211 | color:#FFF; 212 | font-weight:bold; 213 | font-size:large; 214 | margin-bottom: 10px; 215 | text-align: right; 216 | } 217 | .settingsmenuitem{ 218 | color:#CCC; 219 | margin-top: 5px; 220 | text-align: right; 221 | } 222 | .settingsmenuitem a:link, 223 | .settingsmenuitem a:visited{ 224 | color:#CCC; 225 | } 226 | .settingsmenuitem a:hover{ 227 | color:#DDD; 228 | } 229 | .settingsmenuitem-selected{ 230 | color:#C00; 231 | font-weight:bold; 232 | margin-top: 5px; 233 | text-align: right; 234 | } 235 | .settingsmenuitem-selected a:link, 236 | .settingsmenuitem-selected a:visited{ 237 | color:#C00; 238 | } 239 | .settingsmenuitem-selected a:hover{ 240 | color:#D00; 241 | } 242 | .settingsmenuspacer{ 243 | height: 30px; 244 | } 245 | .style9 { 246 | color: #ffffff; 247 | font-size: 11px; 248 | } 249 | .style7 { 250 | font-size: 9px; 251 | color: #666666; 252 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/vending/billing/IabBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.android.vending.billing; 17 | 18 | import android.content.BroadcastReceiver; 19 | import android.content.Context; 20 | import android.content.Intent; 21 | 22 | /** 23 | * Receiver for the "com.android.vending.billing.PURCHASES_UPDATED" Action 24 | * from the Play Store. 25 | * 26 | *

It is possible that an in-app item may be acquired without the 27 | * application calling getBuyIntent(), for example if the item can be 28 | * redeemed from inside the Play Store using a promotional code. If this 29 | * application isn't running at the time, then when it is started a call 30 | * to getPurchases() will be sufficient notification. However, if the 31 | * application is already running in the background when the item is acquired, 32 | * a message to this BroadcastReceiver will indicate that the an item 33 | * has been acquired.

34 | */ 35 | public class IabBroadcastReceiver extends BroadcastReceiver { 36 | /** 37 | * Listener interface for received broadcast messages. 38 | */ 39 | public interface IabBroadcastListener { 40 | void receivedBroadcast(); 41 | } 42 | 43 | /** 44 | * The Intent action that this Receiver should filter for. 45 | */ 46 | public static final String ACTION = "com.android.vending.billing.PURCHASES_UPDATED"; 47 | 48 | private final IabBroadcastListener mListener; 49 | 50 | public IabBroadcastReceiver(IabBroadcastListener listener) { 51 | mListener = listener; 52 | } 53 | 54 | @Override 55 | public void onReceive(Context context, Intent intent) { 56 | if (mListener != null) { 57 | mListener.receivedBroadcast(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/vending/billing/IabException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.android.vending.billing; 17 | 18 | /** 19 | * Exception thrown when something went wrong with in-app billing. 20 | * An IabException has an associated IabResult (an error). 21 | * To get the IAB result that caused this exception to be thrown, 22 | * call {@link #getResult()}. 23 | */ 24 | public class IabException extends Exception { 25 | IabResult mResult; 26 | 27 | public IabException(IabResult r) { 28 | this(r, null); 29 | } 30 | public IabException(int response, String message) { 31 | this(new IabResult(response, message)); 32 | } 33 | public IabException(IabResult r, Exception cause) { 34 | super(r.getMessage(), cause); 35 | mResult = r; 36 | } 37 | public IabException(int response, String message, Exception cause) { 38 | this(new IabResult(response, message), cause); 39 | } 40 | 41 | /** Returns the IAB result (error) that this exception signals. */ 42 | public IabResult getResult() { return mResult; } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/vending/billing/IabResult.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.android.vending.billing; 17 | 18 | /** 19 | * Represents the result of an in-app billing operation. 20 | * A result is composed of a response code (an integer) and possibly a 21 | * message (String). You can get those by calling 22 | * {@link #getResponse} and {@link #getMessage()}, respectively. You 23 | * can also inquire whether a result is a success or a failure by 24 | * calling {@link #isSuccess()} and {@link #isFailure()}. 25 | */ 26 | public class IabResult { 27 | int mResponse; 28 | String mMessage; 29 | 30 | public IabResult(int response, String message) { 31 | mResponse = response; 32 | if (message == null || message.trim().length() == 0) { 33 | mMessage = IabHelper.getResponseDesc(response); 34 | } 35 | else { 36 | mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")"; 37 | } 38 | } 39 | public int getResponse() { return mResponse; } 40 | public String getMessage() { return mMessage; } 41 | public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; } 42 | public boolean isFailure() { return !isSuccess(); } 43 | public String toString() { return "IabResult: " + getMessage(); } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/vending/billing/Inventory.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.android.vending.billing; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** 24 | * Represents a block of information about in-app items. 25 | * An Inventory is returned by such methods as {@link IabHelper#queryInventory}. 26 | */ 27 | public class Inventory { 28 | Map mSkuMap = new HashMap(); 29 | Map mPurchaseMap = new HashMap(); 30 | 31 | Inventory() { } 32 | 33 | /** Returns the listing details for an in-app product. */ 34 | public SkuDetails getSkuDetails(String sku) { 35 | return mSkuMap.get(sku); 36 | } 37 | 38 | /** Returns purchase information for a given product, or null if there is no purchase. */ 39 | public Purchase getPurchase(String sku) { 40 | return mPurchaseMap.get(sku); 41 | } 42 | 43 | /** Returns whether or not there exists a purchase of the given product. */ 44 | public boolean hasPurchase(String sku) { 45 | return mPurchaseMap.containsKey(sku); 46 | } 47 | 48 | /** Return whether or not details about the given product are available. */ 49 | public boolean hasDetails(String sku) { 50 | return mSkuMap.containsKey(sku); 51 | } 52 | 53 | /** 54 | * Erase a purchase (locally) from the inventory, given its product ID. This just 55 | * modifies the Inventory object locally and has no effect on the server! This is 56 | * useful when you have an existing Inventory object which you know to be up to date, 57 | * and you have just consumed an item successfully, which means that erasing its 58 | * purchase data from the Inventory you already have is quicker than querying for 59 | * a new Inventory. 60 | */ 61 | public void erasePurchase(String sku) { 62 | if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku); 63 | } 64 | 65 | /** Returns a list of all owned product IDs. */ 66 | List getAllOwnedSkus() { 67 | return new ArrayList(mPurchaseMap.keySet()); 68 | } 69 | 70 | /** Returns a list of all owned product IDs of a given type */ 71 | List getAllOwnedSkus(String itemType) { 72 | List result = new ArrayList(); 73 | for (Purchase p : mPurchaseMap.values()) { 74 | if (p.getItemType().equals(itemType)) result.add(p.getSku()); 75 | } 76 | return result; 77 | } 78 | 79 | /** Returns a list of all purchases. */ 80 | List getAllPurchases() { 81 | return new ArrayList(mPurchaseMap.values()); 82 | } 83 | 84 | void addSkuDetails(SkuDetails d) { 85 | mSkuMap.put(d.getSku(), d); 86 | } 87 | 88 | void addPurchase(Purchase p) { 89 | mPurchaseMap.put(p.getSku(), p); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/vending/billing/Purchase.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.android.vending.billing; 17 | 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | 21 | /** 22 | * Represents an in-app billing purchase. 23 | */ 24 | public class Purchase { 25 | String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS 26 | String mOrderId; 27 | String mPackageName; 28 | String mSku; 29 | long mPurchaseTime; 30 | int mPurchaseState; 31 | String mDeveloperPayload; 32 | String mToken; 33 | String mOriginalJson; 34 | String mSignature; 35 | boolean mIsAutoRenewing; 36 | 37 | public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException { 38 | mItemType = itemType; 39 | mOriginalJson = jsonPurchaseInfo; 40 | JSONObject o = new JSONObject(mOriginalJson); 41 | mOrderId = o.optString("orderId"); 42 | mPackageName = o.optString("packageName"); 43 | mSku = o.optString("productId"); 44 | mPurchaseTime = o.optLong("purchaseTime"); 45 | mPurchaseState = o.optInt("purchaseState"); 46 | mDeveloperPayload = o.optString("developerPayload"); 47 | mToken = o.optString("token", o.optString("purchaseToken")); 48 | mIsAutoRenewing = o.optBoolean("autoRenewing"); 49 | mSignature = signature; 50 | } 51 | 52 | public String getItemType() { return mItemType; } 53 | public String getOrderId() { return mOrderId; } 54 | public String getPackageName() { return mPackageName; } 55 | public String getSku() { return mSku; } 56 | public long getPurchaseTime() { return mPurchaseTime; } 57 | public int getPurchaseState() { return mPurchaseState; } 58 | public String getDeveloperPayload() { return mDeveloperPayload; } 59 | public String getToken() { return mToken; } 60 | public String getOriginalJson() { return mOriginalJson; } 61 | public String getSignature() { return mSignature; } 62 | public boolean isAutoRenewing() { return mIsAutoRenewing; } 63 | 64 | @Override 65 | public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/vending/billing/SkuDetails.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.android.vending.billing; 17 | 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | 21 | /** 22 | * Represents an in-app product's listing details. 23 | */ 24 | public class SkuDetails { 25 | private final String mItemType; 26 | private final String mSku; 27 | private final String mType; 28 | private final String mPrice; 29 | private final long mPriceAmountMicros; 30 | private final String mPriceCurrencyCode; 31 | private final String mTitle; 32 | private final String mDescription; 33 | private final String mJson; 34 | 35 | public SkuDetails(String jsonSkuDetails) throws JSONException { 36 | this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails); 37 | } 38 | 39 | public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException { 40 | mItemType = itemType; 41 | mJson = jsonSkuDetails; 42 | JSONObject o = new JSONObject(mJson); 43 | mSku = o.optString("productId"); 44 | mType = o.optString("type"); 45 | mPrice = o.optString("price"); 46 | mPriceAmountMicros = o.optLong("price_amount_micros"); 47 | mPriceCurrencyCode = o.optString("price_currency_code"); 48 | mTitle = o.optString("title"); 49 | mDescription = o.optString("description"); 50 | } 51 | 52 | public String getSku() { return mSku; } 53 | public String getType() { return mType; } 54 | public String getPrice() { return mPrice; } 55 | public long getPriceAmountMicros() { return mPriceAmountMicros; } 56 | public String getPriceCurrencyCode() { return mPriceCurrencyCode; } 57 | public String getTitle() { return mTitle; } 58 | public String getDescription() { return mDescription; } 59 | 60 | @Override 61 | public String toString() { 62 | return "SkuDetails:" + mJson; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/Application.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage; 2 | 3 | import android.support.v7.app.AppCompatDelegate; 4 | 5 | import rikka.searchbyimage.staticdata.SearchEngine; 6 | import rikka.searchbyimage.support.Settings; 7 | 8 | /** 9 | * Created by Rikka on 2015/12/31. 10 | */ 11 | public class Application extends android.app.Application { 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | 17 | SearchEngine.getList(this); 18 | 19 | Settings.instance(this).putInt(Settings.LAST_INSTALLED_VERSION, BuildConfig.VERSION_CODE); 20 | // use firebase crash reporting 21 | /*CrashHandler.init(getApplicationContext()); 22 | CrashHandler.register();*/ 23 | 24 | AppCompatDelegate.setDefaultNightMode( 25 | Settings.instance(this).getIntFromString(Settings.NIGHT_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/apdater/FilterAdapterHelper.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.apdater; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.util.SparseBooleanArray; 5 | import android.util.SparseIntArray; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by Rikka on 2016/12/11. 13 | */ 14 | 15 | abstract public class FilterAdapterHelper { 16 | 17 | private RecyclerView.Adapter mAdapter; 18 | 19 | private List mOriginalData; 20 | private List mFilteredData; 21 | 22 | private String mKeyword; 23 | private boolean mIsSearching; 24 | private SparseIntArray mIntKeys; 25 | private SparseBooleanArray mBooleanKeys; 26 | 27 | public FilterAdapterHelper(RecyclerView.Adapter adapter) { 28 | mAdapter = adapter; 29 | mIntKeys = new SparseIntArray(); 30 | mBooleanKeys = new SparseBooleanArray(); 31 | 32 | mOriginalData = new ArrayList<>(); 33 | mFilteredData = new ArrayList<>(); 34 | } 35 | 36 | public List getOriginalData() { 37 | return mOriginalData; 38 | } 39 | 40 | public void setOriginalData(Collection originalData) { 41 | mOriginalData.clear(); 42 | mOriginalData.addAll(originalData); 43 | mFilteredData = filter(mOriginalData); 44 | } 45 | 46 | public void setOriginalData(List originalData) { 47 | mOriginalData = originalData; 48 | mFilteredData = filter(mOriginalData); 49 | } 50 | 51 | public List getFilteredData() { 52 | return mFilteredData; 53 | } 54 | 55 | public T get(int index) { 56 | return mFilteredData.get(index); 57 | } 58 | 59 | public void setKeyword(String keyword) { 60 | if (mKeyword != null && mKeyword.equals(keyword)) { 61 | return; 62 | } 63 | 64 | mKeyword = keyword; 65 | mFilteredData = filter(mOriginalData); 66 | mAdapter.notifyDataSetChanged(); 67 | //mAdapter.requestResetData(); 68 | } 69 | 70 | public void setSearching(boolean searching) { 71 | mIsSearching = searching; 72 | mFilteredData = filter(mOriginalData); 73 | mAdapter.notifyDataSetChanged(); 74 | //mAdapter.requestResetData(); 75 | } 76 | 77 | public void putKey(int key, boolean value) { 78 | putKey(key, value, true); 79 | } 80 | 81 | public void putKey(int key, boolean value, boolean refreshData) { 82 | mBooleanKeys.put(key, value); 83 | if (refreshData) { 84 | mFilteredData = filter(mOriginalData); 85 | } 86 | } 87 | 88 | public void putKey(int key, int value) { 89 | putKey(key, value, true); 90 | } 91 | 92 | public void putKey(int key, int value, boolean refreshData) { 93 | mIntKeys.put(key, value); 94 | if (refreshData) { 95 | mFilteredData = filter(mOriginalData); 96 | } 97 | } 98 | 99 | private List filter(List list) { 100 | List newList = new ArrayList<>(); 101 | 102 | if (list == null) { 103 | return newList; 104 | } 105 | 106 | for (T obj : list) { 107 | boolean check = true; 108 | for (int i = 0; i < mIntKeys.size(); i++) { 109 | int key = mIntKeys.keyAt(i); 110 | if (!check(key, mIntKeys.get(key), obj)) { 111 | check = false; 112 | break; 113 | } 114 | } 115 | for (int i = 0; i < mBooleanKeys.size(); i++) { 116 | int key = mBooleanKeys.keyAt(i); 117 | if (!check(key, mBooleanKeys.get(key), obj)) { 118 | check = false; 119 | break; 120 | } 121 | } 122 | if (!check) { 123 | continue; 124 | } 125 | 126 | if (mIsSearching && !contains(mKeyword, obj)) { 127 | continue; 128 | } 129 | 130 | newList.add(obj); 131 | } 132 | return newList; 133 | } 134 | 135 | abstract public boolean contains(String key, T obj); 136 | 137 | abstract public boolean check(int key, int value, T obj); 138 | 139 | abstract public boolean check(int key, boolean value, T obj); 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/apdater/PostFormAdapter.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.apdater; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.EditText; 8 | import android.widget.LinearLayout; 9 | 10 | import rikka.searchbyimage.R; 11 | import rikka.searchbyimage.staticdata.SearchEngine; 12 | 13 | /** 14 | * Created by Rikka on 2016/1/26. 15 | */ 16 | public class PostFormAdapter extends RecyclerView.Adapter { 17 | public interface OnFocusChangeListener 18 | { 19 | void onFocusChange(View view, boolean hasFocus); 20 | } 21 | 22 | private OnFocusChangeListener mOnFocusChangeListener; 23 | 24 | public void setOnFocusChangeListener(OnFocusChangeListener mOnFocusChangeListener) 25 | { 26 | this.mOnFocusChangeListener = mOnFocusChangeListener; 27 | } 28 | 29 | SearchEngine mData; 30 | boolean mEnabled; 31 | PostFormAdapter mAdapter; 32 | int mCount; 33 | RecyclerView mRecyclerView; 34 | 35 | @Override 36 | public int getItemViewType(int position) { 37 | return position == (getItemCount() - 1) ? 1 : 0; 38 | } 39 | 40 | @Override 41 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 42 | View itemView = LayoutInflater. 43 | from(parent.getContext()). 44 | inflate(viewType == 1 ? R.layout.list_item_post_form_add : R.layout.list_item_post_form, parent, false); 45 | return new ViewHolder(itemView); 46 | } 47 | 48 | @Override 49 | public void onBindViewHolder(ViewHolder holder, final int position) { 50 | if (position == (getItemCount() - 1)) { 51 | if (mEnabled) { 52 | holder.vView.setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | mAdapter.notifyItemInserted(mCount - 1); 56 | mCount++; 57 | } 58 | }); 59 | } else { 60 | holder.vView.setVisibility(View.GONE); 61 | } 62 | 63 | } else { 64 | if (position < mData.post_text_key.size()) { 65 | holder.vKey.setText(mData.post_text_key.get(position)); 66 | holder.vValue.setText(mData.post_text_value.get(position)); 67 | 68 | if (mData.post_text_type.get(position) == -1) { 69 | holder.vValue.setText(R.string.upload_form_built_in_selector); 70 | holder.vValue.setEnabled(false); 71 | } 72 | } 73 | 74 | holder.vKey.setOnFocusChangeListener(new View.OnFocusChangeListener() { 75 | @Override 76 | public void onFocusChange(View v, boolean hasFocus) { 77 | if (mOnFocusChangeListener != null) { 78 | mOnFocusChangeListener.onFocusChange(v, hasFocus); 79 | } 80 | } 81 | }); 82 | holder.vValue.setOnFocusChangeListener(new View.OnFocusChangeListener() { 83 | @Override 84 | public void onFocusChange(View v, boolean hasFocus) { 85 | if (mOnFocusChangeListener != null) { 86 | mOnFocusChangeListener.onFocusChange(v, hasFocus); 87 | } 88 | } 89 | }); 90 | 91 | if (!mEnabled) { 92 | holder.vKey.setEnabled(false); 93 | holder.vValue.setEnabled(false); 94 | } 95 | } 96 | } 97 | 98 | public PostFormAdapter(SearchEngine data, boolean enabled) { 99 | mData = data; 100 | mEnabled = enabled; 101 | mAdapter = this; 102 | mCount = mData.post_text_key.size() + 1; 103 | } 104 | 105 | public PostFormAdapter() { 106 | mData = new SearchEngine(); 107 | mEnabled = true; 108 | mAdapter = this; 109 | mCount = 1; 110 | } 111 | 112 | @Override 113 | public int getItemCount() { 114 | return mCount; 115 | } 116 | 117 | public void setItemCount(int count) { 118 | mCount = count; 119 | } 120 | 121 | public static class ViewHolder extends RecyclerView.ViewHolder { 122 | protected LinearLayout vView; 123 | protected EditText vKey; 124 | protected EditText vValue; 125 | 126 | public ViewHolder(View itemView) { 127 | super(itemView); 128 | 129 | vView = (LinearLayout) itemView.findViewById(R.id.linearLayout); 130 | vKey = (EditText) itemView.findViewById(R.id.editText_key); 131 | vValue = (EditText) itemView.findViewById(R.id.editText_value); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/apdater/ResultAdapter.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.apdater; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.RelativeLayout; 9 | import android.widget.TextView; 10 | 11 | import com.bumptech.glide.Glide; 12 | 13 | import java.util.ArrayList; 14 | 15 | import rikka.searchbyimage.R; 16 | import rikka.searchbyimage.utils.IqdbResultCollecter; 17 | 18 | /** 19 | * Created by Rikka on 2015/12/20. 20 | */ 21 | public class ResultAdapter extends RecyclerView.Adapter { 22 | 23 | private ArrayList mData; 24 | private int mCount = 0; 25 | 26 | public interface OnItemClickListener { 27 | void onItemClick(View view, int position, IqdbResultCollecter.IqdbItem item); 28 | void onItemLongClick(View view , int position, IqdbResultCollecter.IqdbItem item); 29 | } 30 | 31 | private OnItemClickListener mOnItemClickListener; 32 | 33 | public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) { 34 | this.mOnItemClickListener = mOnItemClickListener; 35 | } 36 | 37 | public ResultAdapter(ArrayList mData) { 38 | this.mData = mData; 39 | this.mCount = mData.size(); 40 | } 41 | 42 | @Override 43 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_iqdb_result, parent, false); 45 | return new ViewHolder(view); 46 | } 47 | 48 | @Override 49 | public void onBindViewHolder(final ViewHolder holder, int position) { 50 | IqdbResultCollecter.IqdbItem item = mData.get(position); 51 | holder.mTextViewURL.setText(item.imageURL); 52 | holder.mTextViewSize.setText(item.size); 53 | holder.mTextViewSimilarity.setText(item.similarity); 54 | 55 | Glide.with(holder.mImageView.getContext()) 56 | .load(item.thumbnailURL) 57 | .centerCrop() 58 | .placeholder(R.mipmap.ic_launcher) 59 | .crossFade() 60 | .into(holder.mImageView); 61 | 62 | if (mOnItemClickListener != null) { 63 | holder.itemView.setOnClickListener(new View.OnClickListener() 64 | { 65 | @Override 66 | public void onClick(View v) 67 | { 68 | int pos = holder.getLayoutPosition(); 69 | mOnItemClickListener.onItemClick(holder.itemView, pos, mData.get(pos)); 70 | } 71 | }); 72 | 73 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() 74 | { 75 | @Override 76 | public boolean onLongClick(View v) 77 | { 78 | int pos = holder.getLayoutPosition(); 79 | mOnItemClickListener.onItemLongClick(holder.itemView, pos, mData.get(pos)); 80 | return false; 81 | } 82 | }); 83 | } 84 | } 85 | 86 | @Override 87 | public int getItemCount() { 88 | return mCount; 89 | } 90 | 91 | public static class ViewHolder extends RecyclerView.ViewHolder { 92 | 93 | public RelativeLayout mView; 94 | public TextView mTextViewURL; 95 | public TextView mTextViewSize; 96 | public TextView mTextViewSimilarity; 97 | public ImageView mImageView; 98 | 99 | public ViewHolder(View itemView) { 100 | super(itemView); 101 | 102 | mView = (RelativeLayout) itemView.findViewById(R.id.view); 103 | mTextViewURL = (TextView) itemView.findViewById(R.id.item_text_url); 104 | mTextViewSize = (TextView) itemView.findViewById(R.id.item_text_size); 105 | mTextViewSimilarity = (TextView) itemView.findViewById(R.id.item_text_similarity); 106 | mImageView = (ImageView) itemView.findViewById(R.id.item_image); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/apdater/SimpleAdapter.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.apdater; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by Rikka on 2017/1/22. 8 | */ 9 | 10 | public abstract class SimpleAdapter extends RecyclerView.Adapter { 11 | 12 | public interface OnItemClickListener { 13 | void OnItemClick(int position); 14 | } 15 | 16 | private OnItemClickListener mListener; 17 | 18 | private FilterAdapterHelper mFilterAdapterHelper; 19 | 20 | public SimpleAdapter() { 21 | mFilterAdapterHelper = new FilterAdapterHelper(this) { 22 | @Override 23 | public boolean contains(String key, T obj) { 24 | return true; 25 | } 26 | 27 | @Override 28 | public boolean check(int key, int value, T obj) { 29 | return true; 30 | } 31 | 32 | @Override 33 | public boolean check(int key, boolean value, T obj) { 34 | return true; 35 | } 36 | }; 37 | } 38 | 39 | @Override 40 | public void onBindViewHolder(final VH holder, int position) { 41 | holder.itemView.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | if (mListener != null) { 45 | mListener.OnItemClick(holder.getAdapterPosition()); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | public void setOnItemClickListener(OnItemClickListener listener) { 52 | mListener = listener; 53 | } 54 | 55 | @Override 56 | public int getItemCount() { 57 | return mFilterAdapterHelper.getFilteredData().size(); 58 | } 59 | 60 | public FilterAdapterHelper getHelper() { 61 | return mFilterAdapterHelper; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/database/DatabaseHelper.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.database; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | 7 | import rikka.searchbyimage.database.table.CustomEngineTable; 8 | import rikka.searchbyimage.staticdata.SearchEngine; 9 | 10 | /** 11 | * Created by Rikka on 2016/1/24. 12 | */ 13 | public class DatabaseHelper extends SQLiteOpenHelper { 14 | public static final int DATABASE_VERSION = 3; 15 | public static final String DATABASE_NAME = "search_engines.db"; 16 | 17 | private static DatabaseHelper instance; 18 | 19 | public DatabaseHelper(Context context) { 20 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 21 | } 22 | 23 | public static synchronized DatabaseHelper instance(Context context) { 24 | if (instance == null) { 25 | instance = new DatabaseHelper(context); 26 | } 27 | return instance; 28 | } 29 | 30 | @Override 31 | public void onCreate(SQLiteDatabase db) { 32 | db.execSQL(CustomEngineTable.SQL_CREATE_ENTRIES); 33 | SearchEngine.addBuildInEngines(db); 34 | } 35 | 36 | @Override 37 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 38 | 39 | switch (oldVersion) { 40 | case 1: 41 | db.execSQL("ALTER TABLE " + CustomEngineTable.TABLE_NAME + " ADD " + CustomEngineTable.COLUMN_ENABLED + " integer NOT NULL DEFAULT(1)"); 42 | //do not add break 43 | case 2: 44 | SearchEngine.addBuildInEngines(db); 45 | break; 46 | default: 47 | break; 48 | } 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/database/table/CustomEngineTable.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.database.table; 2 | 3 | /** 4 | * Created by Rikka on 2016/1/24. 5 | */ 6 | public class CustomEngineTable { 7 | public static final String TABLE_NAME = "search_engine"; 8 | 9 | public static final String COLUMN_ID = "id"; 10 | public static final String COLUMN_ENABLED = "enabled"; 11 | public static final String COLUMN_DATA = "data"; 12 | 13 | public static final String SQL_CREATE_ENTRIES = "create table " + TABLE_NAME 14 | + "(" 15 | + COLUMN_ID + " integer primary key," 16 | + COLUMN_ENABLED + " integer," 17 | + COLUMN_DATA + " blob" 18 | + ");"; 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/receiver/ShareBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.receiver; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import rikka.searchbyimage.R; 8 | import rikka.searchbyimage.utils.IntentUtils; 9 | 10 | /** 11 | * Created by Rikka on 2015/12/21. 12 | */ 13 | public class ShareBroadcastReceiver extends BroadcastReceiver { 14 | 15 | @Override 16 | public void onReceive(Context context, Intent intent) { 17 | String url = intent.getDataString(); 18 | 19 | if (url != null) { 20 | Intent shareIntent = new Intent(Intent.ACTION_SEND); 21 | shareIntent.setType("text/plain"); 22 | shareIntent.putExtra(Intent.EXTRA_TEXT, url); 23 | 24 | Intent chooserIntent = Intent.createChooser(shareIntent, context.getString(R.string.share_url)); 25 | chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 26 | IntentUtils.startOtherActivity(context,intent); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/receiver/UploadMessageReceiver.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.receiver; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import rikka.searchbyimage.utils.UploadResultUtils; 8 | 9 | /** 10 | * Created by Yulan on 2016/5/28. 11 | * receiver message which send by {@link rikka.searchbyimage.service.UploadService} 12 | * register in manifest, lower level than {@link rikka.searchbyimage.ui.UploadActivity} 13 | * only when {@link rikka.searchbyimage.ui.UploadActivity} not exist, will received message 14 | */ 15 | 16 | public class UploadMessageReceiver extends BroadcastReceiver { 17 | 18 | @Override 19 | public void onReceive(Context context, Intent intent) { 20 | UploadResultUtils.handleResult(context, intent, true); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/service/UploadParam.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.service; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.support.v4.util.Pair; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by Rikka on 2017/1/21. 12 | */ 13 | 14 | public class UploadParam implements Parcelable { 15 | 16 | private final int mEngineId; 17 | private final int mType; 18 | private final String mFileUri; 19 | private final String mFilename; 20 | private final String mUrl; 21 | private final String mPostFileKey; 22 | private final List> mHeaders; 23 | private final List> mBodies; 24 | private final int mResultOpenAction; 25 | 26 | public int getEngineId() { 27 | return mEngineId; 28 | } 29 | 30 | public int getType() { 31 | return mType; 32 | } 33 | 34 | public String getFileUri() { 35 | return mFileUri; 36 | } 37 | 38 | public String getFilename() { 39 | return mFilename; 40 | } 41 | 42 | public String getUrl() { 43 | return mUrl; 44 | } 45 | 46 | public String getPostFileKey() { 47 | return mPostFileKey; 48 | } 49 | 50 | public List> getHeaders() { 51 | return mHeaders; 52 | } 53 | 54 | public List> getBodies() { 55 | return mBodies; 56 | } 57 | 58 | public int getResultOpenAction() { 59 | return mResultOpenAction; 60 | } 61 | 62 | public UploadParam(int engineId, int type, String fileUri, String fileName, String url, String postFileKey, List> headers, List> bodies, int resultOpenAction) { 63 | mEngineId = engineId; 64 | mType = type; 65 | mFileUri = fileUri; 66 | mFilename = fileName; 67 | mUrl = url; 68 | mPostFileKey = postFileKey; 69 | mHeaders = headers; 70 | mBodies = bodies; 71 | mResultOpenAction = resultOpenAction; 72 | } 73 | 74 | @Override 75 | public int describeContents() { 76 | return 0; 77 | } 78 | 79 | @Override 80 | public void writeToParcel(Parcel dest, int flags) { 81 | dest.writeInt(mEngineId); 82 | dest.writeInt(mType); 83 | dest.writeString(mFileUri); 84 | dest.writeString(mFilename); 85 | dest.writeString(mUrl); 86 | dest.writeString(mPostFileKey); 87 | dest.writeInt(mHeaders.size()); 88 | for (Pair pair : mHeaders) { 89 | dest.writeString(pair.first); 90 | dest.writeString(pair.second); 91 | } 92 | dest.writeInt(mBodies.size()); 93 | for (Pair pair : mBodies) { 94 | dest.writeString(pair.first); 95 | dest.writeString(pair.second); 96 | } 97 | dest.writeInt(mResultOpenAction); 98 | } 99 | 100 | public UploadParam(Parcel in) { 101 | mEngineId = in.readInt(); 102 | mType = in.readInt(); 103 | mFileUri = in.readString(); 104 | mFilename = in.readString(); 105 | mUrl = in.readString(); 106 | mPostFileKey = in.readString(); 107 | 108 | int N; 109 | N = in.readInt(); 110 | mHeaders = new ArrayList<>(N); 111 | for (int i=0; i(in.readString(), in.readString())); 113 | } 114 | 115 | N = in.readInt(); 116 | mBodies = new ArrayList<>(N); 117 | for (int i=0; i(in.readString(), in.readString())); 119 | } 120 | mResultOpenAction = in.readInt(); 121 | } 122 | 123 | 124 | public static final Creator CREATOR = new Creator() { 125 | @Override 126 | public UploadParam createFromParcel(Parcel in) { 127 | return new UploadParam(in); 128 | } 129 | 130 | @Override 131 | public UploadParam[] newArray(int size) { 132 | return new UploadParam[size]; 133 | } 134 | }; 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/service/UploadResult.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.service; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by Rikka on 2017/1/21. 8 | */ 9 | 10 | public class UploadResult implements Parcelable { 11 | 12 | public final static int NO_ERROR = 0; 13 | public final static int CANCELED = -1; 14 | public final static int ERROR_FILE_NOT_FOUND = 1; 15 | public final static int ERROR_UNKNOWN_HOST = 2; 16 | public final static int ERROR_TIMEOUT = 3; 17 | public final static int ERROR_IO = 4; 18 | public final static int ERROR_UNKNOWN = 5; 19 | 20 | private final int mEngineId; 21 | private final String mFileUri; 22 | private final String mFilename; 23 | private final String mUrl; 24 | private final String mHtmlUri; 25 | private final int mResultOpenAction; 26 | private final int mErrorCode; 27 | private final String mErrorMessage; 28 | 29 | public int getEngineId() { 30 | return mEngineId; 31 | } 32 | 33 | public String getFileUri() { 34 | return mFileUri; 35 | } 36 | 37 | public String getFilename() { 38 | return mFilename; 39 | } 40 | 41 | public String getUrl() { 42 | return mUrl; 43 | } 44 | 45 | public String getHtmlUri() { 46 | return mHtmlUri; 47 | } 48 | 49 | public int getResultOpenAction() { 50 | return mResultOpenAction; 51 | } 52 | 53 | public int getErrorCode() { 54 | return mErrorCode; 55 | } 56 | 57 | public String getErrorMessage() { 58 | return mErrorMessage; 59 | } 60 | 61 | public UploadResult(int engineId, String fileUri, String filename, String url, String htmlUri, int resultOpenAction) { 62 | mEngineId = engineId; 63 | mFileUri = fileUri; 64 | mFilename = filename; 65 | mUrl = url; 66 | mHtmlUri = htmlUri; 67 | mResultOpenAction = resultOpenAction; 68 | mErrorCode = NO_ERROR; 69 | mErrorMessage = null; 70 | } 71 | 72 | public UploadResult(int errorCode, String errorMessage, UploadParam param) { 73 | mErrorCode = errorCode; 74 | mErrorMessage = errorMessage; 75 | mEngineId = param == null ? 0 : param.getEngineId(); 76 | mFileUri = param == null ? null : param.getFileUri(); 77 | mFilename = param == null ? null : param.getFilename(); 78 | mUrl = null; 79 | mHtmlUri = null; 80 | mResultOpenAction = 0; 81 | } 82 | 83 | @Override 84 | public int describeContents() { 85 | return 0; 86 | } 87 | 88 | @Override 89 | public void writeToParcel(Parcel dest, int flags) { 90 | dest.writeInt(mErrorCode); 91 | if (mErrorCode != 0) { 92 | dest.writeString(mErrorMessage); 93 | } 94 | dest.writeInt(mEngineId); 95 | dest.writeString(mFileUri); 96 | dest.writeString(mFilename); 97 | dest.writeString(mUrl); 98 | dest.writeString(mHtmlUri); 99 | dest.writeInt(mResultOpenAction); 100 | } 101 | 102 | public UploadResult(Parcel in) { 103 | mErrorCode = in.readInt(); 104 | mErrorMessage = mErrorCode != 0 ? in.readString() : null; 105 | mEngineId = in.readInt(); 106 | mFileUri = in.readString(); 107 | mFilename = in.readString(); 108 | mUrl = in.readString(); 109 | mHtmlUri = in.readString(); 110 | mResultOpenAction = in.readInt(); 111 | } 112 | 113 | 114 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 115 | @Override 116 | public UploadResult createFromParcel(Parcel in) { 117 | return new UploadResult(in); 118 | } 119 | 120 | @Override 121 | public UploadResult[] newArray(int size) { 122 | return new UploadResult[size]; 123 | } 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/staticdata/EngineId.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.staticdata; 2 | 3 | /** 4 | * Created by Rikka on 2016/5/30. 5 | */ 6 | public class EngineId { 7 | public final static int SITE_GOOGLE = 0; 8 | public final static int SITE_BAIDU = 1; 9 | public final static int SITE_IQDB = 2; 10 | public final static int SITE_TINEYE = 3; 11 | public final static int SITE_SAUCENAO = 4; 12 | public final static int SITE_ASCII2D = 5; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/staticdata/SearchEngineParcelable.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.staticdata; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * Created by Rikka on 2016/1/24. 10 | */ 11 | public class SearchEngineParcelable implements Parcelable { 12 | public SearchEngine data = new SearchEngine(); 13 | 14 | @Override 15 | public int describeContents() { 16 | return 0; 17 | } 18 | 19 | @Override 20 | public void writeToParcel(Parcel dest, int flags) { 21 | dest.writeString(data.getName()); 22 | dest.writeString(data.getUploadUrl()); 23 | dest.writeString(data.getPostFileKey()); 24 | dest.writeInt(data.getResultOpenAction()); 25 | dest.writeList(data.post_text_key); 26 | dest.writeList(data.post_text_value); 27 | dest.writeList(data.post_text_type); 28 | } 29 | 30 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 31 | @Override 32 | public SearchEngineParcelable createFromParcel(Parcel source) { 33 | SearchEngineParcelable r = new SearchEngineParcelable(); 34 | r.data.setName(source.readString()); 35 | r.data.setUploadUrl(source.readString()); 36 | r.data.setPostFileKey(source.readString()); 37 | r.data.setResultOpenAction(source.readInt()); 38 | r.data.post_text_key = new ArrayList<>(); 39 | r.data.post_text_value = new ArrayList<>(); 40 | r.data.post_text_type = new ArrayList<>(); 41 | source.readList(r.data.post_text_key, null); 42 | source.readList(r.data.post_text_value, null); 43 | source.readList(r.data.post_text_type, null); 44 | return r; 45 | } 46 | 47 | @Override 48 | public SearchEngineParcelable[] newArray(int size) { 49 | return new SearchEngineParcelable[size]; 50 | } 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/staticdata/StaticData.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.staticdata; 2 | 3 | /** 4 | * Created by Rikka on 2016/6/1. 5 | */ 6 | public class StaticData { 7 | private static StaticData sInstance; 8 | 9 | public static synchronized StaticData instance() { 10 | if (sInstance == null) { 11 | sInstance = new StaticData(); 12 | } 13 | 14 | return sInstance; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/support/CrashHandler.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.support; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageInfo; 6 | import android.os.Build; 7 | 8 | import java.io.File; 9 | import java.io.PrintWriter; 10 | import java.io.StringWriter; 11 | 12 | import rikka.searchbyimage.BuildConfig; 13 | import rikka.searchbyimage.ui.SendReportActivity; 14 | 15 | import static rikka.searchbyimage.support.GetDeviceInfo.getAppInfo; 16 | 17 | public class CrashHandler implements Thread.UncaughtExceptionHandler { 18 | public static String CRASH_DIR; 19 | public static String CRASH_LOG; 20 | 21 | private static String ANDROID = Build.VERSION.RELEASE; 22 | private static String MODEL = Build.MODEL; 23 | private static String MANUFACTURER = Build.MANUFACTURER; 24 | 25 | public static String VERSION = "Unknown"; 26 | 27 | private Thread.UncaughtExceptionHandler mPrevious; 28 | private static Context mContext; 29 | 30 | public static void init(Context context) { 31 | mContext = context; 32 | 33 | try { 34 | PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 35 | VERSION = info.versionName; 36 | 37 | CRASH_DIR = context.getFilesDir().getAbsolutePath() + "/"; 38 | CRASH_LOG = CRASH_DIR + "last_crash.log"; 39 | } catch (Exception e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | 44 | public static void register() { 45 | new CrashHandler(); 46 | } 47 | 48 | private CrashHandler() { 49 | //mPrevious = Thread.currentThread().getUncaughtExceptionHandler(); 50 | mPrevious = Thread.getDefaultUncaughtExceptionHandler(); 51 | //Thread.currentThread().setUncaughtExceptionHandler(this); 52 | Thread.setDefaultUncaughtExceptionHandler(this); 53 | } 54 | 55 | @Override 56 | public void uncaughtException(Thread thread, Throwable throwable) { 57 | File f = new File(CRASH_LOG); 58 | if (f.exists()) { 59 | f.delete(); 60 | } else { 61 | try { 62 | new File(CRASH_DIR).mkdirs(); 63 | f.createNewFile(); 64 | } catch (Exception e) { 65 | return; 66 | } 67 | } 68 | 69 | PrintWriter p; 70 | try { 71 | p = new PrintWriter(f); 72 | } catch (Exception e) { 73 | return; 74 | } 75 | 76 | p.write("Android Version: " + ANDROID + "\n"); 77 | p.write("Device Model: " + MODEL + "\n"); 78 | p.write("Device Manufacturer: " + MANUFACTURER + "\n"); 79 | p.write("App Version: " + VERSION + "\n"); 80 | p.write("*********************\n"); 81 | throwable.printStackTrace(p); 82 | p.close(); 83 | 84 | StringBuilder sb = getAppInfo(mContext); 85 | 86 | StringWriter sw = new StringWriter(); 87 | throwable.printStackTrace(new PrintWriter(sw)); 88 | sb.append(sw.toString()); 89 | 90 | 91 | /*((Application) mContext.getApplicationContext()).getDefaultTracker() 92 | .send(new HitBuilders.ExceptionBuilder() 93 | .setDescription(new StandardExceptionParser(mContext, null) 94 | .getDescription(thread.getName(), throwable)) 95 | .setFatal(true) 96 | .build());*/ 97 | 98 | Intent intent = new Intent(); 99 | intent.setAction(BuildConfig.APPLICATION_ID + ".SEND_LOG"); 100 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 101 | intent.putExtra(SendReportActivity.EXTRA_EMAIL_BODY, sb.toString()); 102 | mContext.startActivity(intent); 103 | 104 | /*if (mPrevious != null) { 105 | mPrevious.uncaughtException(thread, throwable); 106 | }*/ 107 | if (BuildConfig.DEBUG) { 108 | throwable.printStackTrace(); 109 | } 110 | System.exit(1); 111 | } 112 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/support/GetDeviceInfo.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.support; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | 8 | import rikka.searchbyimage.BuildConfig; 9 | 10 | /** 11 | * Created by qixingchen on 16/6/18. 12 | */ 13 | 14 | public class GetDeviceInfo { 15 | 16 | private static String ANDROID = Build.VERSION.RELEASE; 17 | private static String MODEL = Build.MODEL; 18 | private static String MANUFACTURER = Build.MANUFACTURER; 19 | 20 | public static StringBuilder getAppInfo(Context context) { 21 | StringBuilder sb = new StringBuilder(); 22 | sb.append("Android Version: ").append(ANDROID).append("\n"); 23 | sb.append("Device Model: ").append(MODEL).append("\n"); 24 | sb.append("Device Manufacturer: ").append(MANUFACTURER).append("\n"); 25 | sb.append("App Version: ").append(BuildConfig.VERSION_NAME).append("(") 26 | .append(BuildConfig.VERSION_CODE).append(")\n"); 27 | sb.append("Flavor: ").append(BuildConfig.FLAVOR).append("\n"); 28 | 29 | String installerPackageName = context.getPackageManager().getInstallerPackageName(context.getPackageName()); 30 | if (installerPackageName != null) { 31 | sb.append("InstallerPackageName: ").append(installerPackageName).append("\n"); 32 | } 33 | 34 | if (Settings.instance(context) 35 | .getBoolean(Settings.DOWNLOAD_FILE_CRASH, false)) { 36 | sb.append('\n'); 37 | sb.append("Download image url: ").append(Settings.instance(context).getString(Settings.DOWNLOAD_URL, "")).append("\n"); 38 | sb.append("Download image name: ").append(Settings.instance(context).getString(Settings.DOWNLOAD_IMAGE, "")).append("\n"); 39 | } 40 | sb.append("*********************\n"); 41 | return sb; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/support/OkHttpClientProvider.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.support; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import okhttp3.OkHttpClient; 6 | 7 | /** 8 | * Created by Rikka on 2017/1/22. 9 | */ 10 | 11 | public class OkHttpClientProvider { 12 | 13 | private static OkHttpClient sClient; 14 | 15 | public static OkHttpClient get() { 16 | if (sClient == null) { 17 | sClient = new OkHttpClient.Builder() 18 | .writeTimeout(15, TimeUnit.SECONDS) 19 | .connectTimeout(15, TimeUnit.SECONDS) 20 | .readTimeout(60, TimeUnit.SECONDS) 21 | .build(); 22 | } 23 | return sClient; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/support/Settings.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.support; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | /** 8 | * Created by Rikka on 2016/4/3. 9 | */ 10 | public class Settings { 11 | public static final String DOWNLOAD_FILE_CRASH = "download_file_crash"; 12 | public static final String DOWNLOAD_URL = "download_url"; 13 | public static final String DOWNLOAD_IMAGE = "download_image"; 14 | 15 | public static final String SETTINGS_EVERY_TIME = "setting_each_time"; 16 | public static final String SHOW_RESULT_IN = "show_result_in"; 17 | public static final String SHOW_NOTIFICATION = "notification_result"; 18 | public static final String ENGINE_ID = "search_engine_preference"; 19 | public static final String NIGHT_MODE = "night_mode"; 20 | public static final String DONATED = "donated"; 21 | 22 | public static final String LAST_INSTALLED_VERSION = "last_installed_version"; 23 | 24 | public static final String SUCCESSFULLY_UPLOAD_COUNT = "successfully_upload_count"; 25 | public static final String HIDE_DONATE_REQUEST = "hide_donate_request"; 26 | 27 | private static Settings sInstance; 28 | private SharedPreferences mPrefs; 29 | 30 | private Settings(Context context) { 31 | mPrefs = PreferenceManager.getDefaultSharedPreferences(context)/*context.getSharedPreferences(XML_NAME, Context.MODE_PRIVATE)*/; 32 | } 33 | 34 | public static synchronized Settings instance(Context context) { 35 | if (sInstance == null) { 36 | sInstance = new Settings(context.getApplicationContext()); 37 | } 38 | 39 | return sInstance; 40 | } 41 | 42 | public SharedPreferences.Editor edit() { 43 | return mPrefs.edit(); 44 | } 45 | 46 | public Settings putBoolean(String key, boolean value) { 47 | mPrefs.edit() 48 | .putBoolean(key, value) 49 | .apply(); 50 | 51 | return this; 52 | } 53 | 54 | public boolean getBoolean(String key, boolean def) { 55 | return mPrefs.getBoolean(key, def); 56 | } 57 | 58 | public Settings putInt(String key, int value) { 59 | mPrefs.edit() 60 | .putInt(key, value) 61 | .apply(); 62 | 63 | return this; 64 | } 65 | 66 | public int getInt(String key, int defValue) { 67 | return mPrefs.getInt(key, defValue); 68 | } 69 | 70 | public Settings putString(String key, String value) { 71 | mPrefs.edit() 72 | .putString(key, value) 73 | .apply(); 74 | 75 | return this; 76 | } 77 | 78 | public String getString(String key, String defValue) { 79 | return mPrefs.getString(key, defValue); 80 | } 81 | 82 | public Settings putIntToString(String key, int value) { 83 | mPrefs.edit() 84 | .putString(key, Integer.toString(value)) 85 | .apply(); 86 | 87 | return this; 88 | } 89 | 90 | public int getIntFromString(String key, int defValue) { 91 | return Integer.parseInt(mPrefs.getString(key, Integer.toString(defValue))); 92 | } 93 | 94 | /*public Settings putGSON(String key, Object obj) { 95 | mPrefs.edit() 96 | .putString(key, new Gson().toJson(obj)) 97 | .apply(); 98 | 99 | return this; 100 | } 101 | 102 | public T getGSON(String key, Class c) { 103 | T t = new Gson().fromJson(mPrefs.getString(key, ""), c); 104 | return t; 105 | }*/ 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/ui/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.app.AppCompatDelegate; 7 | 8 | /** 9 | * Created by Rikka on 2016/3/3. 10 | */ 11 | public abstract class BaseActivity extends AppCompatActivity { 12 | //private Tracker mTracker; 13 | 14 | @Override 15 | protected void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | //mTracker = ((Application) getApplication()).getDefaultTracker(); 18 | } 19 | 20 | @Override 21 | protected void onResume() { 22 | super.onResume(); 23 | /*mTracker.setScreenName(this.getClass().getSimpleName()); 24 | mTracker.send(new HitBuilders.ScreenViewBuilder().build());*/ 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/ui/BaseResultActivity.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.ui; 2 | 3 | import android.app.ActivityManager; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.content.ContextCompat; 8 | 9 | import rikka.searchbyimage.R; 10 | import rikka.searchbyimage.service.UploadResult; 11 | import rikka.searchbyimage.staticdata.SearchEngine; 12 | import rikka.searchbyimage.utils.UploadResultUtils; 13 | 14 | /** 15 | * Created by Rikka on 2017/1/22. 16 | */ 17 | 18 | public class BaseResultActivity extends BaseActivity { 19 | 20 | public static final String EXTRA_RESULT = 21 | "rikka.searchbyimage.ui.BaseResultActivity.EXTRA_RESULT"; 22 | 23 | protected UploadResult mUploadResult; 24 | protected ActivityManager.TaskDescription mTaskDescription; 25 | 26 | @Override 27 | protected void onCreate(@Nullable Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | 30 | mUploadResult = UploadResultUtils.getResultFromIntent(getIntent(), EXTRA_RESULT); 31 | if (mUploadResult == null) { 32 | return; 33 | } 34 | 35 | setTaskDescription(); 36 | } 37 | 38 | private void setTaskDescription() { 39 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 40 | return; 41 | } 42 | 43 | if (mUploadResult == null) { 44 | return; 45 | } 46 | 47 | if (mTaskDescription == null) { 48 | int siteId = mUploadResult.getEngineId(); 49 | String siteName = null; 50 | if (siteId <= 5) { 51 | siteName = getResources().getStringArray(R.array.search_engines)[siteId]; 52 | } else { 53 | SearchEngine item = SearchEngine.getItemById(siteId); 54 | if (item != null) { 55 | siteName = item.getName(); 56 | } 57 | } 58 | 59 | mTaskDescription = new ActivityManager.TaskDescription( 60 | String.format(getString(R.string.search_result), siteName), 61 | null, 62 | ContextCompat.getColor(this, R.color.colorPrimary)); 63 | } 64 | 65 | setTaskDescription(mTaskDescription); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/ui/ChromeCustomTabsActivity.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.ui; 2 | 3 | import android.net.Uri; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | 7 | import rikka.searchbyimage.utils.BrowsersUtils; 8 | 9 | /** 10 | * Created by Rikka on 2017/1/23. 11 | */ 12 | 13 | public class ChromeCustomTabsActivity extends BaseResultActivity { 14 | 15 | private boolean mPaused; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | 21 | if (mUploadResult == null || TextUtils.isEmpty(mUploadResult.getUrl())) { 22 | finish(); 23 | return; 24 | } 25 | BrowsersUtils.openChrome(this, Uri.parse(mUploadResult.getUrl()), false, mUploadResult); 26 | 27 | //setIntent(new Intent()); 28 | } 29 | 30 | @Override 31 | protected void onPause() { 32 | super.onPause(); 33 | 34 | mPaused = true; 35 | } 36 | 37 | @Override 38 | protected void onResume() { 39 | super.onResume(); 40 | 41 | if (mPaused) { 42 | finish(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/ui/IqdbResultActivity.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.ui; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AlertDialog; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.View; 11 | import android.widget.Toast; 12 | 13 | import java.io.BufferedInputStream; 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.IOException; 17 | import java.nio.charset.Charset; 18 | import java.util.ArrayList; 19 | 20 | import rikka.searchbyimage.R; 21 | import rikka.searchbyimage.apdater.ResultAdapter; 22 | import rikka.searchbyimage.utils.ClipBoardUtils; 23 | import rikka.searchbyimage.utils.IntentUtils; 24 | import rikka.searchbyimage.utils.IqdbResultCollecter; 25 | import rikka.searchbyimage.utils.BrowsersUtils; 26 | 27 | public class IqdbResultActivity extends BaseResultActivity { 28 | 29 | public static final String EXTRA_FILE = 30 | "rikka.searchbyimage.ui.IqdbResultActivity.EXTRA_FILE"; 31 | 32 | RecyclerView mRecyclerView; 33 | ResultAdapter mAdapter; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_result); 39 | 40 | ArrayList list; 41 | 42 | if (!getIntent().hasExtra(EXTRA_FILE)) { 43 | finish(); 44 | } 45 | 46 | list = loadSearchResult(getIntent().getStringExtra(EXTRA_FILE)); 47 | 48 | mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); 49 | 50 | //mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); 51 | /*mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { 52 | });*/ 53 | 54 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 55 | mRecyclerView.setHasFixedSize(true); 56 | 57 | mAdapter = new ResultAdapter(list); 58 | mAdapter.setOnItemClickListener(new ResultAdapter.OnItemClickListener() { 59 | 60 | @Override 61 | public void onItemClick(View view, int position, IqdbResultCollecter.IqdbItem item) { 62 | BrowsersUtils.open(IqdbResultActivity.this, item.imageURL); 63 | } 64 | 65 | @Override 66 | public void onItemLongClick(View view, int position, final IqdbResultCollecter.IqdbItem item) { 67 | new AlertDialog.Builder(IqdbResultActivity.this) 68 | .setItems( 69 | new CharSequence[]{getString(R.string.open_with), getString(R.string.copy_link)}, 70 | new DialogInterface.OnClickListener() { 71 | @Override 72 | public void onClick(DialogInterface dialog, int which) { 73 | switch (which) { 74 | case 0: 75 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(item.imageURL)); 76 | IntentUtils.startOtherActivity(IqdbResultActivity.this, intent); 77 | break; 78 | case 1: 79 | ClipBoardUtils.putTextIntoClipboard(IqdbResultActivity.this, item.imageURL); 80 | Toast.makeText(IqdbResultActivity.this, String.format(getString(R.string.copy_to_clipboard), item.imageURL), Toast.LENGTH_SHORT).show(); 81 | break; 82 | } 83 | } 84 | }) 85 | .show(); 86 | } 87 | }); 88 | 89 | mRecyclerView.setAdapter(mAdapter); 90 | } 91 | 92 | @Override 93 | protected void onNewIntent(Intent intent) { 94 | super.onNewIntent(intent); 95 | } 96 | 97 | private ArrayList loadSearchResult(String htmlFilePath) { 98 | File file = new File(htmlFilePath); 99 | 100 | BufferedInputStream fileStream = null; 101 | StringBuilder sb = new StringBuilder(); 102 | 103 | try { 104 | byte[] buffer = new byte[4096]; 105 | 106 | fileStream = new BufferedInputStream(new FileInputStream(file)); 107 | while ((fileStream.read(buffer)) != -1) { 108 | sb.append(new String(buffer, Charset.forName("UTF-8"))); 109 | } 110 | } catch (IOException e) { 111 | e.printStackTrace(); 112 | } finally { 113 | if (fileStream != null) 114 | try { 115 | fileStream.close(); 116 | } catch (IOException e) { 117 | e.printStackTrace(); 118 | } 119 | } 120 | 121 | return IqdbResultCollecter.getItemList(sb.toString()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/ui/ResultActivity.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | 8 | import rikka.searchbyimage.service.UploadResult; 9 | import rikka.searchbyimage.staticdata.SearchEngine; 10 | import rikka.searchbyimage.utils.BrowsersUtils; 11 | import rikka.searchbyimage.utils.UploadResultUtils; 12 | 13 | public class ResultActivity extends BaseActivity { 14 | 15 | public static final String EXTRA_RESULT = 16 | "rikka.searchbyimage.ui.ResultActivity.EXTRA_RESULT"; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | UploadResult result = UploadResultUtils.getResultFromIntent(getIntent(), EXTRA_RESULT); 23 | if (result == null) { 24 | finish(); 25 | return; 26 | } 27 | 28 | switch (result.getResultOpenAction()) { 29 | case SearchEngine.RESULT_OPEN_ACTION.DEFAULT: 30 | BrowsersUtils.open(this, result.getUrl(), true, result); 31 | break; 32 | case SearchEngine.RESULT_OPEN_ACTION.BUILD_IN_IQDB: 33 | openIqdbResult(this, result); 34 | break; 35 | case SearchEngine.RESULT_OPEN_ACTION.OPEN_HTML_FILE: 36 | openHTMLinWebView(this, result); 37 | break; 38 | } 39 | 40 | setIntent(new Intent()); 41 | } 42 | 43 | @Override 44 | protected void onPostResume() { 45 | super.onPostResume(); 46 | 47 | if (!getIntent().hasExtra(EXTRA_RESULT)) { 48 | finish(); 49 | } 50 | } 51 | 52 | private static void openIqdbResult(Context context, UploadResult result) { 53 | Intent intent = new Intent(context, IqdbResultActivity.class); 54 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 55 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 56 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 57 | } else { 58 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 59 | } 60 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 61 | intent.putExtra(IqdbResultActivity.EXTRA_RESULT, result); 62 | intent.putExtra(IqdbResultActivity.EXTRA_FILE, result.getHtmlUri()); 63 | 64 | context.startActivity(intent); 65 | } 66 | 67 | private static void openHTMLinWebView(Context context, UploadResult result) { 68 | Intent intent = new Intent(context, WebViewActivity.class); 69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 70 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 71 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 72 | } else { 73 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 74 | } 75 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 76 | intent.putExtra(WebViewActivity.EXTRA_RESULT, result); 77 | intent.putExtra(WebViewActivity.EXTRA_FILE, result.getHtmlUri()); 78 | 79 | context.startActivity(intent); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/ui/SendReportActivity.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.ui; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AlertDialog; 8 | 9 | import rikka.searchbyimage.R; 10 | import rikka.searchbyimage.utils.IntentUtils; 11 | 12 | public class SendReportActivity extends BaseActivity { 13 | public static final String EXTRA_EMAIL_BODY = 14 | "rikka.searchbyimage.ui.WebViewActivity.EXTRA_URL"; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | 20 | Intent intent = getIntent(); 21 | if (intent.hasExtra(EXTRA_EMAIL_BODY)) { 22 | handleSendEmail(intent); 23 | } else { 24 | throw new RuntimeException("Crash test!"); 25 | } 26 | } 27 | 28 | private void handleSendEmail(final Intent intent) { 29 | new AlertDialog.Builder(this) 30 | .setTitle(R.string.app_crash_title) 31 | .setMessage(R.string.app_crash_message) 32 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 33 | @Override 34 | public void onClick(DialogInterface dialog, int which) { 35 | sendEmail(intent.getStringExtra(EXTRA_EMAIL_BODY)); 36 | finish(); 37 | } 38 | }) 39 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 40 | @Override 41 | public void onClick(DialogInterface dialog, int which) { 42 | finish(); 43 | } 44 | }) 45 | .setOnDismissListener(new DialogInterface.OnDismissListener() { 46 | @Override 47 | public void onDismiss(DialogInterface dialog) { 48 | finish(); 49 | } 50 | }) 51 | .show(); 52 | } 53 | 54 | private void sendEmail(String body) { 55 | Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "rikkanyaaa+imageSearchBugReport@gmail.com", null)); 56 | intent.putExtra(Intent.EXTRA_CC, new String[]{"xmu.miffy+imageSearchBugReport@gmail.com"}); 57 | intent.putExtra(Intent.EXTRA_SUBJECT, "SearchByImage crash log"); 58 | intent.putExtra(Intent.EXTRA_TEXT, body); 59 | IntentUtils.startOtherActivity(this, intent); 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/ArrayUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import java.lang.reflect.Array; 4 | 5 | /** 6 | * Created by Rikka on 2016/8/28. 7 | */ 8 | public class ArrayUtils { 9 | 10 | public static T[] add(T[] source, Class cls, T obj) { 11 | return add(source, cls, obj, source.length); 12 | } 13 | 14 | public static T[] add(T[] source, Class cls, T obj, int index) { 15 | T[] result = (T[]) Array.newInstance(cls, source.length + 1); 16 | System.arraycopy(source, 0, result, 0, index); 17 | source[index] = obj; 18 | System.arraycopy(source, index + 1, result, index + 1, source.length - index); 19 | return result; 20 | } 21 | 22 | public static T[] remove(T[] source, Class cls, int index) { 23 | T[] result = (T[]) Array.newInstance(cls, source.length - 1); 24 | System.arraycopy(source, 0, result, 0, index); 25 | System.arraycopy(source, index + 1, result, index, source.length - index - 1); 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/ClipBoardUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.content.ClipData; 4 | import android.content.Context; 5 | import android.content.ClipboardManager; 6 | 7 | /** 8 | * Created by Rikka on 2015/12/18. 9 | */ 10 | public class ClipBoardUtils { 11 | public static void putTextIntoClipboard(Context context, String text) { 12 | ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 13 | ClipData clipData = ClipData.newPlainText("copy text", text); 14 | clipboardManager.setPrimaryClip(clipData); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/DatabindingHelper.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.support.v7.widget.SwitchCompat; 5 | import android.widget.ImageView; 6 | 7 | import com.bumptech.glide.Glide; 8 | 9 | /** 10 | * Created by Yulan on 2016/1/30. 11 | */ 12 | public class DatabindingHelper { 13 | 14 | @BindingAdapter("imageUrl") 15 | public static void setimageUrl(ImageView imageView, String imageUrl) { 16 | Glide.with(imageView.getContext()) 17 | .load(imageUrl) 18 | .crossFade() 19 | .into(imageView); 20 | } 21 | 22 | @BindingAdapter("OnCheckedChangeListener") 23 | public static void setOnCheckedChangeListener(SwitchCompat switchCompat, SwitchCompat.OnCheckedChangeListener onCheckedChangeListener) { 24 | switchCompat.setOnCheckedChangeListener(onCheckedChangeListener); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/DownloadManagerResolver.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.content.ActivityNotFoundException; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.support.v7.app.AlertDialog; 11 | import android.support.v7.widget.AppCompatTextView; 12 | 13 | import rikka.searchbyimage.R; 14 | 15 | public final class DownloadManagerResolver { 16 | 17 | private static final String DOWNLOAD_MANAGER_PACKAGE_NAME = "com.android.providers.downloads"; 18 | 19 | /** 20 | * Resolve whether the DownloadManager is enable in current devices. 21 | * 22 | * @return true if DownloadManager is enable,false otherwise. 23 | */ 24 | public static boolean resolve(Context context) { 25 | boolean enable = resolveEnable(context); 26 | if (!enable) { 27 | AlertDialog alertDialog = createDialog(context); 28 | alertDialog.show(); 29 | } 30 | return enable; 31 | } 32 | 33 | /** 34 | * Resolve whether the DownloadManager is enable in current devices. 35 | * 36 | * @param context 37 | * @return true if DownloadManager is enable,false otherwise. 38 | */ 39 | private static boolean resolveEnable(Context context) { 40 | int state = context.getPackageManager() 41 | .getApplicationEnabledSetting(DOWNLOAD_MANAGER_PACKAGE_NAME); 42 | 43 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { 44 | return !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || 45 | state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 46 | || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); 47 | } else { 48 | return !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || 49 | state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); 50 | } 51 | } 52 | 53 | private static AlertDialog createDialog(final Context context) { 54 | AppCompatTextView messageTextView = new AppCompatTextView(context); 55 | messageTextView.setTextSize(16f); 56 | messageTextView.setText(R.string.download_manager_disabled); 57 | return new AlertDialog.Builder(context) 58 | .setView(messageTextView, 50, 30, 50, 30) 59 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 60 | @Override 61 | public void onClick(DialogInterface dialog, int which) { 62 | enableDownloadManager(context); 63 | } 64 | }) 65 | .setNegativeButton(android.R.string.cancel,null) 66 | .setCancelable(false) 67 | .create(); 68 | } 69 | 70 | /** 71 | * Start activity to enable DownloadManager in Settings. 72 | */ 73 | private static void enableDownloadManager(Context context) { 74 | try { 75 | //Open the specific App Info page: 76 | Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 77 | intent.setData(Uri.parse("package:" + DOWNLOAD_MANAGER_PACKAGE_NAME)); 78 | IntentUtils.startOtherActivity(context,intent); 79 | } catch (ActivityNotFoundException e) { 80 | e.printStackTrace(); 81 | 82 | //Open the generic Apps page: 83 | Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS); 84 | IntentUtils.startOtherActivity(context,intent); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/FilenameResolver.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.content.ContentResolver; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.provider.OpenableColumns; 7 | 8 | /** 9 | * Created by Rikka on 2017/1/22. 10 | */ 11 | 12 | public final class FilenameResolver { 13 | 14 | public static String query(ContentResolver contentResolver, Uri uri) { 15 | if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { 16 | return uri.getLastPathSegment(); 17 | } else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { 18 | String filename = null; 19 | Cursor cursor = contentResolver.query(uri, null, null, null, null); 20 | if (cursor != null && cursor.moveToFirst()) { 21 | int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 22 | filename = cursor.getString(nameIndex); 23 | cursor.close(); 24 | } 25 | return filename; 26 | } 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.net.FileNameMap; 6 | import java.net.URLConnection; 7 | 8 | /** 9 | * Created by Rikka on 2016/1/22. 10 | */ 11 | public class HttpUtils { 12 | 13 | public static String guessMimeType(String path) { 14 | FileNameMap fileNameMap = URLConnection.getFileNameMap(); 15 | String contentTypeFor; 16 | try { 17 | contentTypeFor = fileNameMap.getContentTypeFor(path); 18 | } catch (Exception e) { 19 | contentTypeFor = "application/octet-stream"; 20 | } 21 | 22 | if (TextUtils.isEmpty(contentTypeFor)) { 23 | contentTypeFor = "application/octet-stream"; 24 | } 25 | return contentTypeFor; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/ImageUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.support.annotation.Nullable; 6 | import android.util.Log; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * Created by Rikka on 2016/1/6. 14 | */ 15 | public class ImageUtils { 16 | 17 | @Nullable 18 | public static byte[] resizeImage(@Nullable InputStream inputStream, int maxSize) throws IOException { 19 | if (inputStream == null) { 20 | return null; 21 | } 22 | 23 | BitmapFactory.Options options = new BitmapFactory.Options(); 24 | options.inSampleSize = calculateInSampleSize(inputStream.available(), maxSize); 25 | 26 | Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options); 27 | if (bitmap == null) { 28 | return null; 29 | } 30 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 31 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos); 32 | byte[] bytes = bos.toByteArray(); 33 | 34 | Log.d("ResizeImage", "inSampleSize = " + options.inSampleSize); 35 | 36 | return bytes/*new ByteArrayInputStream(bytes)*/; 37 | } 38 | 39 | private static int calculateInSampleSize(int size, int maxSize) { 40 | // 糟糕的大概计算... 41 | int curSize = size; 42 | int inSampleSize = 1; 43 | 44 | while (curSize > maxSize) { 45 | inSampleSize *= 2; 46 | curSize /= 4; 47 | } 48 | 49 | return inSampleSize; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/IntentUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.content.pm.ResolveInfo; 8 | import android.widget.Toast; 9 | 10 | import java.util.List; 11 | 12 | import rikka.searchbyimage.R; 13 | 14 | /** 15 | * Created by Rikka on 2015/12/28. 16 | */ 17 | public class IntentUtils { 18 | public static int getSize(Context context, Intent intent) { 19 | PackageManager packageManager = context.getPackageManager(); 20 | List activities = packageManager.queryIntentActivities(intent, 0); 21 | return activities.size(); 22 | } 23 | 24 | public static boolean canOpenWith(Context context, Intent intent) { 25 | return canOpenWith(context, intent, 0); 26 | } 27 | 28 | public static boolean canOpenWith(Context context, Intent intent, int minSize) { 29 | PackageManager packageManager = context.getPackageManager(); 30 | List activities = packageManager.queryIntentActivities(intent, 0); 31 | return activities.size() > minSize; 32 | } 33 | 34 | /** 35 | * start other app's activity 36 | * may not found 37 | */ 38 | public static void startOtherActivity(Context context, Intent intent) { 39 | startOtherActivity(context, intent, context.getString(R.string.target_app_not_found)); 40 | } 41 | 42 | public static void startOtherActivity(Context context, Intent intent, String notFoundMessage) { 43 | if (canOpenWith(context, intent)) { 44 | if (!(context instanceof Activity)) { 45 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 46 | } 47 | context.startActivity(intent); 48 | } else { 49 | Toast.makeText(context, notFoundMessage, Toast.LENGTH_LONG).show(); 50 | } 51 | } 52 | 53 | public static void startOtherActivityForResult(Activity activity, Intent intent, int requestCode) { 54 | startOtherActivityForResult(activity, intent, requestCode, activity.getString(R.string.target_app_not_found)); 55 | } 56 | 57 | public static void startOtherActivityForResult(Activity activity, Intent intent, int requestCode, String notFoundMessage) { 58 | if (canOpenWith(activity, intent)) { 59 | activity.startActivityForResult(intent, requestCode); 60 | } else { 61 | Toast.makeText(activity, notFoundMessage, Toast.LENGTH_LONG).show(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/IqdbResultCollecter.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import java.io.InputStream; 4 | import java.util.ArrayList; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * Created by Rikka on 2015/12/20. 10 | */ 11 | public class IqdbResultCollecter { 12 | public static class IqdbItem { 13 | public String thumbnailURL; 14 | public String imageURL; 15 | public String size; 16 | public String similarity; 17 | 18 | IqdbItem() { 19 | 20 | } 21 | 22 | IqdbItem(String thumbnailURL, String imageURL, String size, String similarity) { 23 | this.thumbnailURL = thumbnailURL; 24 | this.imageURL = imageURL; 25 | this.size = size; 26 | this.similarity = similarity; 27 | } 28 | } 29 | 30 | private static final String FIND_HEAD[] = { 31 | "
", 32 | "
Best match
", 33 | "
Additional match
", 34 | "
Possible match
" 35 | }; 36 | private static final String FIND_END = "similarity
"; 37 | 38 | private static final String URL_HEAD = ""; 40 | 41 | private static final String THUMBNAIL_HEAD = " getItemList(String string) { 46 | ArrayList list = new ArrayList<>(); 47 | 48 | int findStart = 0, findEnd = 0; 49 | 50 | for (String aFIND_HEAD : FIND_HEAD) { 51 | findStart = string.indexOf(aFIND_HEAD, findStart); 52 | 53 | while (findStart != -1) 54 | { 55 | String lineString = string.substring(findStart, string.indexOf(FIND_END, findStart) + "similarity".length()); 56 | 57 | IqdbItem item = new IqdbItem(); 58 | 59 | findStart = string.indexOf(URL_HEAD, findStart) + URL_HEAD.length(); 60 | findEnd = string.indexOf(URL_END, findStart); 61 | item.imageURL = fixURL(string.substring(findStart, findEnd)); 62 | 63 | findStart = string.indexOf(THUMBNAIL_HEAD, findStart) + THUMBNAIL_HEAD.length(); 64 | findEnd = string.indexOf(THUMBNAIL_END, findStart); 65 | item.thumbnailURL = "http://iqdb.org" + string.substring(findStart, findEnd); 66 | 67 | Pattern r; 68 | Matcher m; 69 | 70 | r = Pattern.compile("[0-9]\\d+×[0-9]\\d+"); 71 | m = r.matcher(lineString); 72 | if (m.find()) { 73 | item.size = m.group(0); 74 | } 75 | 76 | r = Pattern.compile("[0-9]\\d{1,3}% similarity"); 77 | m = r.matcher(lineString); 78 | if (m.find()) { 79 | item.similarity = m.group(0).substring(0, m.group(0).length() - " similarity".length()); 80 | } 81 | 82 | list.add(item); 83 | 84 | findStart = string.indexOf(aFIND_HEAD, findStart); 85 | } 86 | } 87 | 88 | return list; 89 | } 90 | 91 | private static String fixURL(String URL) { 92 | if (!URL.startsWith("http") && !URL.startsWith("https")) { 93 | return "http:" + URL; 94 | } 95 | 96 | return URL; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/PackageUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | 7 | /** 8 | * Created by Rikka on 2016/12/3. 9 | */ 10 | 11 | public class PackageUtils { 12 | 13 | public static boolean isPackageInstalled(Context context, String packageName) { 14 | try { 15 | context.getPackageManager().getPackageInfo(packageName, 0); 16 | return true; 17 | } catch (PackageManager.NameNotFoundException e) { 18 | return false; 19 | } 20 | } 21 | 22 | public static boolean isPackageEnabled(Context context, String packageName) { 23 | try { 24 | return context.getPackageManager().getApplicationInfo(packageName, 0).enabled; 25 | } catch (PackageManager.NameNotFoundException e) { 26 | return false; 27 | } 28 | } 29 | 30 | public static Intent getLaunchIntent(Context context, String packageName) { 31 | if (!isPackageEnabled(context, packageName)) { 32 | return null; 33 | } 34 | 35 | try { 36 | return context.getPackageManager().getLaunchIntentForPackage(packageName); 37 | } catch (Exception ignored) { 38 | return null; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/ParcelableUtils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by Rikka on 2016/1/24. 8 | */ 9 | public class ParcelableUtils { 10 | public static byte[] marshall(Parcelable parceable) { 11 | Parcel parcel = Parcel.obtain(); 12 | parceable.writeToParcel(parcel, 0); 13 | byte[] bytes = parcel.marshall(); 14 | parcel.recycle(); // not sure if needed or a good idea 15 | return bytes; 16 | } 17 | 18 | public static T unmarshall(byte[] bytes, Parcelable.Creator creator) { 19 | Parcel parcel = unmarshall(bytes); 20 | return creator.createFromParcel(parcel); 21 | } 22 | 23 | public static Parcel unmarshall(byte[] bytes) { 24 | Parcel parcel = Parcel.obtain(); 25 | parcel.unmarshall(bytes, 0, bytes.length); 26 | parcel.setDataPosition(0); // this is extremely important! 27 | return parcel; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * Created by Rikka on 2016/1/10. 14 | */ 15 | public class Utils { 16 | public static int dpToPx(int dp) { 17 | return (int) (dp * Resources.getSystem().getDisplayMetrics().density); 18 | } 19 | 20 | public static File streamToCacheFile(Context context, InputStream inputStream, String name) { 21 | return streamToCacheFile(context, inputStream, "", name); 22 | } 23 | 24 | public static File streamToCacheFile(Context context, InputStream inputStream, String path, String name) { 25 | String FilePath = context.getCacheDir().getAbsolutePath() + "/" + path + "/" + name; 26 | 27 | return streamToFile(inputStream, FilePath); 28 | } 29 | 30 | public static File streamToFile(InputStream inputStream, String path) { 31 | File file = new File(path); 32 | if (!file.getParentFile().exists()) { 33 | //noinspection ResultOfMethodCallIgnored 34 | file.getParentFile().mkdirs(); 35 | } 36 | try { 37 | //noinspection ResultOfMethodCallIgnored 38 | file.createNewFile(); 39 | 40 | FileOutputStream outputStream = new FileOutputStream(path); 41 | 42 | int bytesRead; 43 | byte[] buffer = new byte[1024]; 44 | while ((bytesRead = inputStream.read(buffer)) != -1) { 45 | outputStream.write(buffer, 0, bytesRead); 46 | } 47 | } catch (IOException e) { 48 | e.printStackTrace(); 49 | } 50 | return file; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/view/ContextMenuTitleView.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.view; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | import android.widget.ScrollView; 6 | import android.widget.TextView; 7 | 8 | import rikka.searchbyimage.R; 9 | import rikka.searchbyimage.utils.Utils; 10 | 11 | /** 12 | * Created by Rikka on 2016/1/7. 13 | */ 14 | public class ContextMenuTitleView extends ScrollView { 15 | private static final int MAX_HEIGHT_DP = 70; 16 | private static final int PADDING_DP = 16; 17 | 18 | public ContextMenuTitleView(Context context, String title) { 19 | super(context); 20 | 21 | int padding = Utils.dpToPx(PADDING_DP); 22 | setPadding(padding, padding, padding, 0); 23 | 24 | TextView titleView = new TextView(context); 25 | titleView.setText(title); 26 | titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); 27 | titleView.setTextColor(context.getResources().getColor(R.color.primary_text)); 28 | addView(titleView); 29 | } 30 | 31 | @Override 32 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 33 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(Utils.dpToPx(MAX_HEIGHT_DP), MeasureSpec.AT_MOST); 34 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/view/InfoBarLayout.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.drawable.ColorDrawable; 8 | import android.graphics.drawable.Drawable; 9 | import android.os.Build; 10 | import android.support.v4.content.ContextCompat; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.widget.FrameLayout; 14 | import android.widget.RelativeLayout; 15 | 16 | import rikka.searchbyimage.R; 17 | 18 | /** 19 | * Created by Rikka on 2016/1/18. 20 | */ 21 | public class InfoBarLayout extends FrameLayout { 22 | public interface OnLayoutChangeListener { 23 | void onLayoutChange(View view, int left, int top, int right, int bottom); 24 | } 25 | 26 | private OnLayoutChangeListener mOnLayoutChangeListener; 27 | 28 | private Drawable mLineDrawable; 29 | 30 | public InfoBarLayout(Context context) { 31 | super(context); 32 | } 33 | 34 | public InfoBarLayout(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | 37 | mLineDrawable = new ColorDrawable(ContextCompat.getColor(context, R.color.divider)); 38 | } 39 | 40 | public void setOnLayoutChangeListener(OnLayoutChangeListener onLayoutChangeListener) { 41 | mOnLayoutChangeListener = onLayoutChangeListener; 42 | } 43 | 44 | @Override 45 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 46 | super.onLayout(changed, l, t, r, b); 47 | if (changed && mOnLayoutChangeListener != null) { 48 | mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b); 49 | } 50 | } 51 | 52 | @Override 53 | protected void onDraw(Canvas canvas) { 54 | super.onDraw(canvas); 55 | 56 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 57 | mLineDrawable.setBounds(0, 0, canvas.getWidth(), 2); 58 | mLineDrawable.draw(canvas); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/view/WebViewToolBar.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.os.Handler; 7 | import android.support.v4.content.ContextCompat; 8 | import android.support.v7.widget.Toolbar; 9 | import android.util.AttributeSet; 10 | 11 | import rikka.searchbyimage.R; 12 | import rikka.searchbyimage.utils.Utils; 13 | 14 | /** 15 | * Created by Rikka on 2016/1/10. 16 | */ 17 | public class WebViewToolBar extends Toolbar { 18 | private Context mContext; 19 | private Paint mPaint; 20 | 21 | private int progress; 22 | private boolean drawProgress; 23 | 24 | private String mTitle; 25 | private String mURL; 26 | 27 | public WebViewToolBar(Context context) { 28 | this(context, null); 29 | } 30 | 31 | public WebViewToolBar(Context context, AttributeSet attrs) { 32 | this(context, attrs, android.support.v7.appcompat.R.attr.toolbarStyle); 33 | } 34 | 35 | public WebViewToolBar(Context context, AttributeSet attrs, int defStyleAttr) { 36 | super(context, attrs, defStyleAttr); 37 | 38 | mContext = context; 39 | mPaint = new Paint(); 40 | 41 | setTitleTextAppearance(mContext, R.style.WebView_Title); 42 | setSubtitleTextAppearance(mContext, R.style.WebView_Uri); 43 | } 44 | 45 | @Override 46 | public void onDraw(Canvas canvas) { 47 | super.onDraw(canvas); 48 | 49 | if (!drawProgress) { 50 | return; 51 | } 52 | mPaint.setColor(ContextCompat.getColor(mContext, R.color.progressBarDark)); 53 | canvas.drawRect(0, canvas.getHeight() - Utils.dpToPx(2), canvas.getWidth(), canvas.getHeight(), mPaint); 54 | mPaint.setColor(ContextCompat.getColor(mContext, R.color.progressBar)); 55 | canvas.drawRect(0, canvas.getHeight() - Utils.dpToPx(2), canvas.getWidth() * (float) progress / 100f, canvas.getHeight(), mPaint); 56 | invalidate(); 57 | } 58 | 59 | public int getProgress() { 60 | return progress; 61 | } 62 | 63 | public void setProgress(int progress) { 64 | this.progress = progress; 65 | 66 | if (progress == 100) { 67 | new Handler().postDelayed(new Runnable(){ 68 | public void run() { 69 | drawProgress = false; 70 | } 71 | }, 800); 72 | }/* else { 73 | drawProgress = true; 74 | }*/ 75 | } 76 | 77 | public boolean getCanDrawProgress() { 78 | return drawProgress; 79 | } 80 | 81 | public void setCanDrawProgress(boolean drawProgress) { 82 | this.drawProgress = drawProgress; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/viewholder/ListBottomSheetItemViewHolder.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.viewholder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.TextView; 6 | 7 | /** 8 | * Created by Rikka on 2017/1/22. 9 | */ 10 | 11 | public class ListBottomSheetItemViewHolder extends RecyclerView.ViewHolder { 12 | 13 | public TextView text; 14 | 15 | public ListBottomSheetItemViewHolder(View itemView) { 16 | super(itemView); 17 | 18 | text = (TextView) itemView.findViewById(android.R.id.text1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/widget/DropDown.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.v7.widget.AppCompatSpinner; 6 | import android.util.AttributeSet; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.AdapterView; 11 | import android.widget.FrameLayout; 12 | import android.widget.SpinnerAdapter; 13 | import android.widget.TextView; 14 | 15 | import rikka.searchbyimage.R; 16 | import rikka.searchbyimage.utils.Utils; 17 | 18 | /** 19 | * Created by Rikka on 2016/1/29. 20 | */ 21 | public class DropDown extends FrameLayout { 22 | private Context mContext; 23 | //private ArrayAdapter mAdapter; 24 | private SpinnerAdapter mAdapter; 25 | private AppCompatSpinner mSpinner; 26 | private TextView mTitleTextView; 27 | private TextView mSummaryTextView; 28 | 29 | private String mTitle; 30 | private String mSummary; 31 | 32 | 33 | public DropDown(Context context) { 34 | super(context, null); 35 | } 36 | 37 | public DropDown(Context context, AttributeSet attrs) { 38 | super(context, attrs); 39 | setPadding(0, Utils.dpToPx(8), 0, Utils.dpToPx(8)); 40 | setMinimumHeight(Utils.dpToPx(48)); 41 | 42 | mContext = context; 43 | 44 | //mAdapter = new ArrayAdapter<>(mContext, 45 | // android.R.layout.simple_spinner_dropdown_item); 46 | mSpinner = new AppCompatSpinner(context); 47 | //mSpinner.setAdapter(mAdapter); 48 | mSpinner.setPadding(Utils.dpToPx(20), 0, 0, 0); 49 | mSpinner.setVisibility(INVISIBLE); 50 | mSpinner.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT)); 51 | mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 52 | @Override 53 | public void onItemSelected(AdapterView parent, View v, int position, long id) { 54 | setSelection(position); 55 | } 56 | 57 | @Override 58 | public void onNothingSelected(AdapterView parent) { 59 | } 60 | }); 61 | addView(mSpinner); 62 | 63 | setOnClickListener(new OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | mSpinner.performClick(); 67 | } 68 | }); 69 | 70 | LayoutInflater.from(context).inflate(R.layout.drop_down, this); 71 | 72 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DropDown); 73 | mTitle = a.getString(R.styleable.DropDown_myTitle); 74 | a.recycle(); 75 | 76 | mTitleTextView = (TextView) findViewById(android.R.id.title); 77 | mTitleTextView.setSingleLine(true); 78 | mTitleTextView.setText(mTitle); 79 | 80 | mSummaryTextView = (TextView) findViewById(android.R.id.summary); 81 | mSummaryTextView.setSingleLine(true); 82 | } 83 | 84 | @Override 85 | public void setEnabled(boolean enabled) { 86 | super.setEnabled(enabled); 87 | 88 | mTitleTextView.setEnabled(enabled); 89 | mSummaryTextView.setEnabled(enabled); 90 | } 91 | 92 | public void setTitle(String title) { 93 | mTitle = title; 94 | mTitleTextView.setText(title); 95 | } 96 | 97 | public void setSummary(String summary) { 98 | mSummary = summary; 99 | mSummaryTextView.setText(summary); 100 | } 101 | 102 | public void setSelection(int position) { 103 | setSummary(mAdapter.getItem(position).toString()); 104 | } 105 | 106 | public int getSelectedItemPosition() { 107 | return mSpinner.getSelectedItemPosition(); 108 | } 109 | 110 | public void setAdapter(SpinnerAdapter adapter) { 111 | mAdapter = adapter; 112 | mSpinner.setAdapter(adapter); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/widget/FABAwareScrollingViewBehavior.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.widget; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.AppBarLayout; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.v4.view.ViewCompat; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by Rikka on 2017/1/23. 15 | */ 16 | 17 | public class FABAwareScrollingViewBehavior 18 | extends AppBarLayout.ScrollingViewBehavior { 19 | public FABAwareScrollingViewBehavior(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | @Override 24 | public boolean layoutDependsOn(CoordinatorLayout parent, 25 | View child, View dependency) { 26 | return super.layoutDependsOn(parent, child, dependency) || 27 | dependency instanceof FloatingActionButton; 28 | } 29 | 30 | @Override 31 | public boolean onStartNestedScroll( 32 | final CoordinatorLayout coordinatorLayout, final View child, 33 | final View directTargetChild, final View target, 34 | final int nestedScrollAxes) { 35 | // Ensure we react to vertical scrolling 36 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL 37 | || super.onStartNestedScroll(coordinatorLayout, child, 38 | directTargetChild, target, nestedScrollAxes); 39 | } 40 | 41 | @Override 42 | public void onNestedScroll( 43 | final CoordinatorLayout coordinatorLayout, final View child, 44 | final View target, final int dxConsumed, final int dyConsumed, 45 | final int dxUnconsumed, final int dyUnconsumed) { 46 | super.onNestedScroll(coordinatorLayout, child, target, 47 | dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 48 | if (dyConsumed > 0) { 49 | // User scrolled down -> hide the FAB 50 | List dependencies = coordinatorLayout.getDependencies(child); 51 | for (View view : dependencies) { 52 | if (view instanceof FloatingActionButton) { 53 | ((FloatingActionButton) view).hide(); 54 | } 55 | } 56 | } else if (dyConsumed < 0) { 57 | // User scrolled up -> show the FAB 58 | List dependencies = coordinatorLayout.getDependencies(child); 59 | for (View view : dependencies) { 60 | if (view instanceof FloatingActionButton) { 61 | ((FloatingActionButton) view).show(); 62 | } 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/rikka/searchbyimage/widget/MyLinearLayoutManager.java: -------------------------------------------------------------------------------- 1 | package rikka.searchbyimage.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import rikka.searchbyimage.utils.Utils; 10 | 11 | /** 12 | * Created by Rikka on 2016/1/27. 13 | */ 14 | public class MyLinearLayoutManager extends LinearLayoutManager { 15 | private final int DEFAULT_CHILD_HEIGHT = Utils.dpToPx(48); 16 | private int mFakeItemCount = 0; 17 | 18 | public MyLinearLayoutManager(Context context) { 19 | super(context); 20 | } 21 | 22 | public void setFakeItemCount(int fakeItemCount) { 23 | mFakeItemCount = fakeItemCount; 24 | } 25 | 26 | @Override 27 | public boolean canScrollVertically() { 28 | return false; 29 | } 30 | 31 | @Override 32 | public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, 33 | int widthSpec, int heightSpec) { 34 | final int width = View.MeasureSpec.getSize(widthSpec); 35 | int height = DEFAULT_CHILD_HEIGHT * mFakeItemCount; 36 | int childHeight = DEFAULT_CHILD_HEIGHT; 37 | for (int i = 0; i < getItemCount(); i++) { 38 | try { 39 | childHeight = measureScrapChildHeight(recycler, i, 40 | View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), 41 | View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED)); 42 | height = height + childHeight; 43 | 44 | } catch (IndexOutOfBoundsException ignore) { 45 | height = height + childHeight; 46 | } 47 | } 48 | setMeasuredDimension(width, height); 49 | } 50 | 51 | private int measureScrapChildHeight(RecyclerView.Recycler recycler, int position, int widthSpec, 52 | int heightSpec) throws IndexOutOfBoundsException { 53 | View view = recycler.getViewForPosition(position); 54 | int height = 0; 55 | if (view != null) { 56 | 57 | RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); 58 | int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, 59 | view.getPaddingLeft() + view.getPaddingRight(), p.width); 60 | int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, 61 | view.getPaddingTop() + view.getPaddingBottom(), p.height); 62 | view.measure(childWidthSpec, childHeightSpec); 63 | height = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; 64 | recycler.recycleView(view); 65 | } 66 | return height; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/animator/press_hide.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/animator/raise.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 10 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/color-night/primary_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/primary_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-hdpi/ic_stat.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_stat_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-hdpi/ic_stat_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-mdpi/ic_stat.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_stat_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-mdpi/ic_stat_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-xhdpi/ic_stat.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_stat_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-xhdpi/ic_stat_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-xxhdpi/ic_stat.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_stat_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-xxhdpi/ic_stat_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-xxxhdpi/ic_stat.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_stat_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable-xxxhdpi/ic_stat_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/check.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cloud_upload.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_all_out_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_backup_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cloud_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cloud_upload_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_crop_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file_upload_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_list_bulleted_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_get_app_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_help_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_icon_google_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_icon_other_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_insert_link_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo_alipay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mode_edit_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_vert_not_press_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_vert_pressed_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_vert_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_open_in_browser_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_open_in_new_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_qu_upload.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_security_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_web_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/infobar_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable/infobar_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/infobar_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/SearchByImage/6334e3181a54456fde5cbeb58d1f1047d23edb75/app/src/main/res/drawable/infobar_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/line_divider_search_engine_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/plus.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_edit_sites.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | 17 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 25 | 26 | 27 | 28 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_sheet_list_ltem.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/drop_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 25 | 26 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/infobar.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 33 | 34 | 40 | 41 |