├── Ninja ├── .idea │ ├── .name │ ├── copyright │ │ └── profiles_settings.xml │ ├── dictionaries │ │ └── matthew.xml │ ├── vcs.xml │ ├── modules.xml │ ├── compiler.xml │ └── misc.xml ├── libs │ └── android-support-v4.jar ├── res │ ├── drawable-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_action_cl_dark.png │ │ ├── ic_action_clear.png │ │ ├── ic_action_cl_active.png │ │ ├── ic_action_cl_light.png │ │ ├── ic_action_done_all.png │ │ ├── ic_action_up_active.png │ │ ├── ic_action_add_active.png │ │ ├── ic_action_add_default.png │ │ ├── ic_action_background.png │ │ ├── ic_action_down_active.png │ │ ├── ic_action_down_default.png │ │ ├── ic_action_up_default.png │ │ ├── ic_notification_ninja.png │ │ ├── ic_action_history_active.png │ │ ├── ic_action_refresh_active.png │ │ ├── ic_action_setting_active.png │ │ ├── ic_action_history_default.png │ │ ├── ic_action_overflow_active.png │ │ ├── ic_action_overflow_default.png │ │ ├── ic_action_refresh_default.png │ │ ├── ic_action_setting_default.png │ │ ├── ic_action_bookmark_active_blue.png │ │ ├── ic_action_bookmark_active_dark.png │ │ ├── ic_action_bookmark_default_blue.png │ │ ├── ic_action_bookmark_default_dark.png │ │ └── ic_action_bookmark_default_light.png │ ├── drawable-xxxhdpi │ │ └── ic_launcher.png │ ├── values │ │ ├── id.xml │ │ ├── integers.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── styles.xml │ │ ├── strings_key.xml │ │ └── arrays.xml │ ├── menu │ │ ├── token_menu.xml │ │ ├── setting_menu.xml │ │ ├── clear_menu.xml │ │ ├── whilelist_menu.xml │ │ └── readability_menu.xml │ ├── xml │ │ ├── searchable.xml │ │ └── preference_clear.xml │ ├── drawable │ │ ├── album_shape_blue.xml │ │ ├── album_shape_dark.xml │ │ ├── grid_item_shape.xml │ │ ├── round_corner_shape_light.xml │ │ ├── round_corner_shape_white.xml │ │ ├── shadow_above.xml │ │ ├── shadow_below.xml │ │ ├── drop_down_shape.xml │ │ ├── cl_selector_dark.xml │ │ ├── up_selector.xml │ │ ├── add_selector.xml │ │ ├── cl_selector_light.xml │ │ ├── down_selector.xml │ │ ├── history_selector.xml │ │ ├── refresh_selector.xml │ │ ├── setting_selector.xml │ │ ├── overflow_selector.xml │ │ ├── bookmark_selector_blue.xml │ │ ├── bookmark_selector_dark.xml │ │ ├── bookmark_selector_light.xml │ │ └── progress_bar_layer_list.xml │ ├── anim │ │ ├── album_slide_in_up.xml │ │ └── album_fade_out.xml │ ├── layout-v21 │ │ ├── dialog_list.xml │ │ ├── dialog_desc.xml │ │ └── dialog_edit.xml │ ├── layout │ │ ├── dialog_list.xml │ │ ├── dialog_desc.xml │ │ ├── dialog_text_item.xml │ │ ├── record_list.xml │ │ ├── dialog_edit.xml │ │ ├── whitelist_item.xml │ │ ├── readability.xml │ │ ├── grid_item.xml │ │ ├── complete_item.xml │ │ ├── home.xml │ │ ├── album.xml │ │ ├── dialog_sign_in.xml │ │ ├── dialog_license_item.xml │ │ ├── token.xml │ │ ├── record_item.xml │ │ └── whitelist.xml │ ├── values-zh-rCN │ │ └── arrays.xml │ ├── values-zh-rTW │ │ └── arrays.xml │ ├── values-fr │ │ └── arrays.xml │ └── values-v21 │ │ └── styles.xml ├── src │ ├── io │ │ └── github │ │ │ └── mthli │ │ │ └── Ninja │ │ │ ├── Browser │ │ │ ├── AlbumController.java │ │ │ ├── NinjaClickHandler.java │ │ │ ├── NinjaGestureListener.java │ │ │ ├── BrowserController.java │ │ │ ├── NinjaDownloadListener.java │ │ │ ├── BrowserContainer.java │ │ │ ├── NinjaWebChromeClient.java │ │ │ └── AdBlock.java │ │ │ ├── View │ │ │ ├── FullscreenHolder.java │ │ │ ├── NinjaContextWrapper.java │ │ │ ├── NinjaToast.java │ │ │ ├── GridItem.java │ │ │ ├── DialogAdapter.java │ │ │ ├── RecordAdapter.java │ │ │ ├── GridAdapter.java │ │ │ ├── WhitelistAdapter.java │ │ │ ├── NinjaRelativeLayout.java │ │ │ ├── Album.java │ │ │ ├── UserAgentListPreference.java │ │ │ ├── SearchEngineListPreference.java │ │ │ └── SwipeToBoundListener.java │ │ │ ├── Database │ │ │ ├── Record.java │ │ │ └── RecordHelper.java │ │ │ ├── Fragment │ │ │ └── ClearFragment.java │ │ │ ├── Task │ │ │ ├── ExportBookmarksTask.java │ │ │ ├── ExportWhitelistTask.java │ │ │ ├── ImportBookmarksTask.java │ │ │ ├── ImportWhitelistTask.java │ │ │ ├── ScreenshotTask.java │ │ │ └── ReadabilityTask.java │ │ │ ├── Service │ │ │ ├── ClearService.java │ │ │ └── HolderService.java │ │ │ ├── Unit │ │ │ ├── RecordUnit.java │ │ │ ├── NotificationUnit.java │ │ │ ├── IntentUnit.java │ │ │ └── ViewUnit.java │ │ │ └── Activity │ │ │ ├── TokenActivity.java │ │ │ ├── SettingActivity.java │ │ │ ├── ClearActivity.java │ │ │ └── WhitelistActivity.java │ └── org │ │ └── askerov │ │ └── dynamicgrid │ │ ├── DynamicGridAdapterInterface.java │ │ ├── DynamicGridUtils.java │ │ ├── AbstractDynamicGridAdapter.java │ │ └── BaseDynamicGridAdapter.java ├── project.properties ├── ant.properties ├── proguard-project.txt ├── Ninja.iml ├── assets │ ├── ninja_introduction_zh.md │ ├── ninja_introduction_en.md │ ├── ninja_introduction_zh.html │ └── ninja_introduction_en.html ├── build.xml └── AndroidManifest.xml ├── Art ├── icon │ ├── Ninja.ai │ ├── Ninja.png │ └── Ninja.psd ├── info │ └── reject.png └── screenshot │ ├── dribbble.png │ ├── overflow.png │ ├── settings.png │ ├── background.png │ ├── .directory │ └── tab_switcher.png └── .gitignore /Ninja/.idea/.name: -------------------------------------------------------------------------------- 1 | Ninja -------------------------------------------------------------------------------- /Art/icon/Ninja.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/icon/Ninja.ai -------------------------------------------------------------------------------- /Art/icon/Ninja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/icon/Ninja.png -------------------------------------------------------------------------------- /Art/icon/Ninja.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/icon/Ninja.psd -------------------------------------------------------------------------------- /Art/info/reject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/info/reject.png -------------------------------------------------------------------------------- /Art/screenshot/dribbble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/screenshot/dribbble.png -------------------------------------------------------------------------------- /Art/screenshot/overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/screenshot/overflow.png -------------------------------------------------------------------------------- /Art/screenshot/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/screenshot/settings.png -------------------------------------------------------------------------------- /Art/screenshot/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/screenshot/background.png -------------------------------------------------------------------------------- /Art/screenshot/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | PreviewsShown=true 3 | Timestamp=2015,4,26,11,40,51 4 | Version=3 5 | -------------------------------------------------------------------------------- /Art/screenshot/tab_switcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Art/screenshot/tab_switcher.png -------------------------------------------------------------------------------- /Ninja/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/libs/android-support-v4.jar -------------------------------------------------------------------------------- /Ninja/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Ninja/.idea/dictionaries/matthew.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_cl_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_cl_dark.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_clear.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_cl_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_cl_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_cl_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_cl_light.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_done_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_done_all.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_up_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_up_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_add_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_add_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_add_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_add_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_background.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_down_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_down_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_down_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_down_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_up_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_up_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_notification_ninja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_notification_ninja.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_history_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_history_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_refresh_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_refresh_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_setting_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_setting_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_history_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_history_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_overflow_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_overflow_active.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_overflow_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_overflow_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_refresh_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_refresh_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_setting_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_setting_default.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_bookmark_active_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_bookmark_active_blue.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_bookmark_active_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_bookmark_active_dark.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_bookmark_default_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_bookmark_default_blue.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_bookmark_default_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_bookmark_default_dark.png -------------------------------------------------------------------------------- /Ninja/res/drawable-xxhdpi/ic_action_bookmark_default_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthli/Ninja/master/Ninja/res/drawable-xxhdpi/ic_action_bookmark_default_light.png -------------------------------------------------------------------------------- /Ninja/res/values/id.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/res/menu/token_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Ninja/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Ninja/res/menu/setting_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Ninja/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0 6 | 255 7 | 8 | -------------------------------------------------------------------------------- /Ninja/res/xml/searchable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /Ninja/res/drawable/album_shape_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/res/drawable/album_shape_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/res/drawable/grid_item_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/res/drawable/round_corner_shape_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/res/drawable/round_corner_shape_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Ninja/res/drawable/shadow_above.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Ninja/res/drawable/shadow_below.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Ninja/res/anim/album_slide_in_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Ninja/res/anim/album_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /Ninja/res/menu/clear_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Ninja/res/menu/whilelist_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Ninja/res/drawable/drop_down_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Ninja/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Ninja/res/menu/readability_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder 24 | proguard/ 25 | proguard_logs/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Others 31 | *.directory 32 | -------------------------------------------------------------------------------- /Ninja/res/drawable/cl_selector_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/up_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/add_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/cl_selector_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/down_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/history_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/refresh_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/setting_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/AlbumController.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.graphics.Bitmap; 4 | import android.view.View; 5 | 6 | public interface AlbumController { 7 | int getFlag(); 8 | 9 | void setFlag(int flag); 10 | 11 | View getAlbumView(); 12 | 13 | void setAlbumCover(Bitmap bitmap); 14 | 15 | String getAlbumTitle(); 16 | 17 | void setAlbumTitle(String title); 18 | 19 | void activate(); 20 | 21 | void deactivate(); 22 | } 23 | -------------------------------------------------------------------------------- /Ninja/res/drawable/overflow_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/bookmark_selector_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/bookmark_selector_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/res/drawable/bookmark_selector_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/FullscreenHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | import android.widget.FrameLayout; 6 | 7 | public class FullscreenHolder extends FrameLayout { 8 | public FullscreenHolder(Context context) { 9 | super(context); 10 | this.setBackgroundColor(context.getResources().getColor(android.R.color.black)); 11 | } 12 | 13 | @Override 14 | public boolean onTouchEvent(MotionEvent event) { 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Ninja/res/layout-v21/dialog_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Ninja/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-22 15 | proguard.config=proguard.cfg -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/NinjaClickHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | import io.github.mthli.Ninja.View.NinjaWebView; 6 | 7 | public class NinjaClickHandler extends Handler { 8 | private NinjaWebView webView; 9 | 10 | public NinjaClickHandler(NinjaWebView webView) { 11 | super(); 12 | this.webView = webView; 13 | } 14 | 15 | @Override 16 | public void handleMessage(Message message) { 17 | super.handleMessage(message); 18 | webView.getBrowserController().onLongPress(message.getData().getString("url")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/NinjaContextWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.content.ContextWrapper; 5 | import android.content.res.Resources; 6 | import io.github.mthli.Ninja.R; 7 | 8 | public class NinjaContextWrapper extends ContextWrapper { 9 | private Context context; 10 | 11 | public NinjaContextWrapper(Context context) { 12 | super(context); 13 | this.context = context; 14 | this.context.setTheme(R.style.BrowserActivityTheme); 15 | } 16 | 17 | @Override 18 | public Resources.Theme getTheme() { 19 | return context.getTheme(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ninja/res/drawable/progress_bar_layer_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Ninja/res/layout/dialog_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Ninja/res/layout/dialog_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Ninja/res/layout-v21/dialog_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Ninja/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Ninja/ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /Ninja/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/NinjaGestureListener.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.view.GestureDetector; 4 | import android.view.MotionEvent; 5 | import io.github.mthli.Ninja.View.NinjaWebView; 6 | 7 | public class NinjaGestureListener extends GestureDetector.SimpleOnGestureListener { 8 | private NinjaWebView webView; 9 | private boolean longPress = true; 10 | 11 | public NinjaGestureListener(NinjaWebView webView) { 12 | super(); 13 | this.webView = webView; 14 | } 15 | 16 | @Override 17 | public void onLongPress(MotionEvent e) { 18 | if (longPress) { 19 | webView.onLongPress(); 20 | } 21 | } 22 | 23 | @Override 24 | public boolean onDoubleTapEvent(MotionEvent e) { 25 | longPress = false; 26 | return false; 27 | } 28 | 29 | @Override 30 | public void onShowPress(MotionEvent e) { 31 | longPress = true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Database/Record.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Database; 2 | 3 | public class Record { 4 | private String title; 5 | public String getTitle() { 6 | return title; 7 | } 8 | public void setTitle(String title) { 9 | this.title = title; 10 | } 11 | 12 | private String url; 13 | public String getURL() { 14 | return url; 15 | } 16 | public void setURL(String url) { 17 | this.url = url; 18 | } 19 | 20 | private long time; 21 | public long getTime() { 22 | return time; 23 | } 24 | public void setTime(long time) { 25 | this.time = time; 26 | } 27 | 28 | public Record() { 29 | this.title = null; 30 | this.url = null; 31 | this.time = 0l; 32 | } 33 | 34 | public Record(String title, String url, long time) { 35 | this.title = title; 36 | this.url = url; 37 | this.time = time; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Ninja/res/layout/dialog_text_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/NinjaToast.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.widget.Toast; 6 | 7 | public class NinjaToast { 8 | private static Toast toast; 9 | private static Handler handler = new Handler(); 10 | private static Runnable runnable = new Runnable() { 11 | @Override 12 | public void run() { 13 | toast.cancel(); 14 | } 15 | }; 16 | 17 | public static void show(Context context, int stringResId) { 18 | show(context, context.getString(stringResId)); 19 | } 20 | 21 | public static void show(Context context, String text) { 22 | handler.removeCallbacks(runnable); 23 | if (toast != null) { 24 | toast.setText(text); 25 | } else { 26 | toast = Toast.makeText(context, text, Toast.LENGTH_SHORT); 27 | } 28 | handler.postDelayed(runnable, 2000); 29 | toast.show(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Ninja/Ninja.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Ninja/src/org/askerov/dynamicgrid/DynamicGridAdapterInterface.java: -------------------------------------------------------------------------------- 1 | package org.askerov.dynamicgrid; 2 | 3 | /** 4 | * Author: alex askerov 5 | * Date: 18/07/14 6 | * Time: 23:44 7 | */ 8 | 9 | /** 10 | * Any adapter used with DynamicGridView must implement DynamicGridAdapterInterface. 11 | * Adapter implementation also must has stable items id. 12 | * See {@link org.askerov.dynamicgrid.AbstractDynamicGridAdapter} for stable id implementation example. 13 | */ 14 | 15 | public interface DynamicGridAdapterInterface { 16 | 17 | /** 18 | * Determines how to reorder items dragged from originalPosition to newPosition 19 | */ 20 | void reorderItems(int originalPosition, int newPosition); 21 | 22 | /** 23 | * @return return columns number for GridView. Need for compatibility 24 | * (@link android.widget.GridView#getNumColumns() requires api 11) 25 | */ 26 | int getColumnCount(); 27 | 28 | /** 29 | * Determines whether the item in the specified position can be reordered. 30 | */ 31 | boolean canReorder(int position); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Fragment/ClearFragment.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Fragment; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceFragment; 6 | import io.github.mthli.Ninja.R; 7 | 8 | public class ClearFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | addPreferencesFromResource(R.xml.preference_clear); 13 | } 14 | 15 | @Override 16 | public void onResume() { 17 | super.onResume(); 18 | SharedPreferences sp = getPreferenceScreen().getSharedPreferences(); 19 | sp.registerOnSharedPreferenceChangeListener(this); 20 | } 21 | 22 | @Override 23 | public void onPause() { 24 | super.onPause(); 25 | getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); 26 | } 27 | 28 | @Override 29 | public void onSharedPreferenceChanged(SharedPreferences sp, String key) {} 30 | } 31 | -------------------------------------------------------------------------------- /Ninja/res/layout/record_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Database/RecordHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Database; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | import android.support.annotation.NonNull; 7 | import io.github.mthli.Ninja.Unit.RecordUnit; 8 | 9 | public class RecordHelper extends SQLiteOpenHelper { 10 | private static final String DATABASE_NAME = "Ninja3.db"; 11 | private static final int DATABASE_VERSION = 1; 12 | 13 | public RecordHelper(Context context) { 14 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 15 | } 16 | 17 | @Override 18 | public void onCreate(SQLiteDatabase database) { 19 | database.execSQL(RecordUnit.CREATE_BOOKMARKS); 20 | database.execSQL(RecordUnit.CREATE_HISTORY); 21 | database.execSQL(RecordUnit.CREATE_WHITELIST); 22 | database.execSQL(RecordUnit.CREATE_GRID); 23 | } 24 | 25 | // UPGRADE ATTENTION!!! 26 | @Override 27 | public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {} 28 | 29 | // UPGRADE ATTENTION!!! 30 | private boolean isTableExist(@NonNull String tableName) { 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/BrowserController.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.net.Uri; 4 | import android.os.Message; 5 | import android.view.View; 6 | import android.webkit.ValueCallback; 7 | import android.webkit.WebChromeClient; 8 | import android.webkit.WebView; 9 | 10 | public interface BrowserController { 11 | void updateAutoComplete(); 12 | 13 | void updateBookmarks(); 14 | 15 | void updateInputBox(String query); 16 | 17 | void updateProgress(int progress); 18 | 19 | void showAlbum(AlbumController albumController, boolean anim, boolean expand, boolean capture); 20 | 21 | void removeAlbum(AlbumController albumController); 22 | 23 | void openFileChooser(ValueCallback uploadMsg); 24 | 25 | void showFileChooser(ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams); 26 | 27 | void onCreateView(WebView view, Message resultMsg); 28 | 29 | boolean onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback); 30 | 31 | boolean onShowCustomView(View view, WebChromeClient.CustomViewCallback callback); 32 | 33 | boolean onHideCustomView(); 34 | 35 | void onLongPress(String url); 36 | } 37 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/GridItem.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | public class GridItem { 4 | private String title; 5 | public String getTitle() { 6 | return title; 7 | } 8 | public void setTitle(String title) { 9 | this.title = title; 10 | } 11 | 12 | private String url; 13 | public String getURL() { 14 | return url; 15 | } 16 | public void setURL(String url) { 17 | this.url = url; 18 | } 19 | 20 | private String filename; 21 | public String getFilename() { 22 | return filename; 23 | } 24 | public void setFilename(String filename) { 25 | this.filename = filename; 26 | } 27 | 28 | private int ordinal; 29 | public int getOrdinal() { 30 | return ordinal; 31 | } 32 | public void setOrdinal(int ordinal) { 33 | this.ordinal = ordinal; 34 | } 35 | 36 | public GridItem() { 37 | this.title = null; 38 | this.url = null; 39 | this.filename = null; 40 | this.ordinal = -1; 41 | } 42 | 43 | public GridItem(String title, String url, String filename, int ordinal) { 44 | this.title = title; 45 | this.url = url; 46 | this.filename = filename; 47 | this.ordinal = ordinal; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Ninja/res/layout/dialog_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Ninja/res/layout-v21/dialog_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Ninja/res/layout/whitelist_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 16 | 17 | 18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Ninja/res/layout/readability.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | 20 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Ninja/assets/ninja_introduction_zh.md: -------------------------------------------------------------------------------- 1 | Ninja使用指南 2 | === 3 | 4 | __作者要准备期末考试了,所以可能暂停更新__。 5 | 6 | Ninja在操作上与其他浏览器并没有明显的区别,但是仍然有以下值得注意的地方: 7 | 8 | ## 自定义主页: 9 | 10 | - 当主页上没有添加任何东西时,主页将显示为`about:blank`。 11 | 12 | - 当你在浏览网页时,点击地址栏的菜单按钮,选择__添加拨号__即可将当前浏览网页添加到主页中。 13 | 14 | - 当你在主页时,点击地址栏的菜单按钮,选择__排版__即可对主页上的拨号卡片进行__排序__。__长按__拨号卡片则可对其进行__标题编辑__等。 15 | 16 | ## 切换标签页: 17 | 18 | - 你可以在`设置/浏览/地址栏位置`中设置地址栏显示在__屏幕顶部__或__屏幕底部__。 19 | 20 | - 按住地址栏__向下拖拽__或__向上拖拽__即可显示标签页切换器。 21 | 22 | - __上下滑动__标签关闭网页。 23 | 24 | - 你也可以__左右滑动地址栏__来切换标签页。 25 | 26 | 地址栏顶部或底部左侧的__齿轮__图标即为__设置__选项。 27 | 28 | __注意__,当软键盘处于展开状态时,标签页切换器将不会工作以防止误操作。 29 | 30 | ## 在其他App中点击链接实现后台加载: 31 | 32 | 1. 在其他App中点击链接时请将Ninja设置为__默认浏览器__。 33 | 34 | 2. __单击__链接Ninja将自动在后台加载,并在通知栏显示相应状态。 35 | 36 | 3. __双击__链接Ninja将会弹出对话框,你可以选择跳转到前台加载或选择其他操作。 37 | 38 | ## 音量键控制: 39 | 40 | 在`设置/浏览/音量键控制`中你可以设定音量键的行为: 41 | 42 | - 切换标签页。 43 | 44 | - 滚动网页。 45 | 46 | - 系统默认的音量控制。 47 | 48 | ## AdBlock白名单: 49 | 50 | 开启AdBlock功能以后,有一些网站可能会因为AdBlock而无法正常地显示内容。 51 | 52 | 你可以在`设置/Adblock/白名单`中将这些网站加入AdBlock的白名单。 53 | 54 | ## 阅读模式: 55 | 56 | Ninja支持阅读模式,但是实现这个功能需要__Readability的令牌__。 57 | 58 | 你可以前往[Readability Developer APIs](https://www.readability.com/developers/api "Readability Developer APIs")获取你自己的令牌并在`设置/阅读模式/口令`中设置。 59 | 60 | ## 网页截图: 61 | 62 | Ninja提供了网页截图功能,可以截取整张网页。 63 | 64 | 但是这并不意味着你可以截取相当相当相当长的网页而不出现问题,可能会因为__OOM__等原因而崩溃。 65 | 66 | --- 67 | 68 | _2015.06.19 Matthew Lee_ 69 | -------------------------------------------------------------------------------- /Ninja/res/layout/grid_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 18 | 19 | 20 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Ninja/res/layout/complete_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 18 | 19 | 20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/DialogAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.TextView; 9 | import io.github.mthli.Ninja.R; 10 | 11 | import java.util.List; 12 | 13 | public class DialogAdapter extends ArrayAdapter { 14 | private Context context; 15 | private int layoutResId; 16 | private List list; 17 | 18 | public DialogAdapter(Context context, int layoutResId, List list) { 19 | super(context, layoutResId, list); 20 | this.context = context; 21 | this.layoutResId = layoutResId; 22 | this.list = list; 23 | } 24 | 25 | private static class Holder { 26 | TextView textView; 27 | } 28 | 29 | @Override 30 | public View getView(int position, View convertView, ViewGroup parent) { 31 | Holder holder; 32 | View view = convertView; 33 | 34 | if (view == null) { 35 | view = LayoutInflater.from(context).inflate(layoutResId, parent, false); 36 | holder = new Holder(); 37 | holder.textView = (TextView) view.findViewById(R.id.dialog_text_item); 38 | view.setTag(holder); 39 | } else { 40 | holder = (Holder) view.getTag(); 41 | } 42 | 43 | holder.textView.setText(list.get(position)); 44 | 45 | return view; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Ninja/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #0D47A1 6 | #1565C0 7 | #1976D2 8 | #1E88E5 9 | #2196F3 10 | #42A5F5 11 | #64B5F6 12 | #90CAF9 13 | #BBDEFB 14 | 15 | #212121 16 | #424242 17 | #616161 18 | #757575 19 | #9E9E9E 20 | #BDBDBD 21 | #E0E0E0 22 | #EEEEEE 23 | #F5F5F5 24 | 25 | #000000 26 | #CC212121 27 | #44212121 28 | @color/gray_900 29 | @color/gray_100 30 | #F3F3F3 31 | #FFFFFF 32 | @android:color/transparent 33 | 34 | #212121 35 | #727272 36 | #B8B8B8 37 | #DADADA 38 | 39 | -------------------------------------------------------------------------------- /Ninja/res/layout/home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 21 | 22 | 23 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Ninja/src/org/askerov/dynamicgrid/DynamicGridUtils.java: -------------------------------------------------------------------------------- 1 | package org.askerov.dynamicgrid; 2 | 3 | import android.view.View; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Author: alex askerov 9 | * Date: 9/7/13 10 | * Time: 10:14 PM 11 | */ 12 | public class DynamicGridUtils { 13 | /** 14 | * Delete item in list from position indexFrom and insert it to indexTwo 15 | * 16 | * @param list 17 | * @param indexFrom 18 | * @param indexTwo 19 | */ 20 | public static void reorder(List list, int indexFrom, int indexTwo) { 21 | Object obj = list.remove(indexFrom); 22 | list.add(indexTwo, obj); 23 | } 24 | 25 | /** 26 | * Swap item in list at position firstIndex with item at position secondIndex 27 | * 28 | * @param list The list in which to swap the items. 29 | * @param firstIndex The position of the first item in the list. 30 | * @param secondIndex The position of the second item in the list. 31 | */ 32 | public static void swap(List list, int firstIndex, int secondIndex) { 33 | Object firstObject = list.get(firstIndex); 34 | Object secondObject = list.get(secondIndex); 35 | list.set(firstIndex, secondObject); 36 | list.set(secondIndex, firstObject); 37 | } 38 | 39 | public static float getViewX(View view) { 40 | return Math.abs((view.getRight() - view.getLeft()) / 2); 41 | } 42 | 43 | public static float getViewY(View view) { 44 | return Math.abs((view.getBottom() - view.getTop()) / 2); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Ninja/res/layout/album.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 16 | 17 | 18 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Task/ExportBookmarksTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Task; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.os.AsyncTask; 6 | import io.github.mthli.Ninja.R; 7 | import io.github.mthli.Ninja.Unit.BrowserUnit; 8 | import io.github.mthli.Ninja.View.NinjaToast; 9 | 10 | public class ExportBookmarksTask extends AsyncTask { 11 | private Context context; 12 | private ProgressDialog dialog; 13 | private String path; 14 | 15 | public ExportBookmarksTask(Context context) { 16 | this.context = context; 17 | this.dialog = null; 18 | this.path = null; 19 | } 20 | 21 | @Override 22 | protected void onPreExecute() { 23 | dialog = new ProgressDialog(context); 24 | dialog.setCancelable(false); 25 | dialog.setMessage(context.getString(R.string.toast_wait_a_minute)); 26 | dialog.show(); 27 | } 28 | 29 | @Override 30 | protected Boolean doInBackground(Void... params) { 31 | path = BrowserUnit.exportBookmarks(context); 32 | 33 | if (isCancelled()) { 34 | return false; 35 | } 36 | return path != null && !path.isEmpty(); 37 | } 38 | 39 | @Override 40 | protected void onPostExecute(Boolean result) { 41 | dialog.hide(); 42 | dialog.dismiss(); 43 | 44 | if (result) { 45 | NinjaToast.show(context, context.getString(R.string.toast_export_bookmarks_successful) + path); 46 | } else { 47 | NinjaToast.show(context, R.string.toast_export_bookmarks_failed); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Task/ExportWhitelistTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Task; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.os.AsyncTask; 6 | import io.github.mthli.Ninja.R; 7 | import io.github.mthli.Ninja.Unit.BrowserUnit; 8 | import io.github.mthli.Ninja.View.NinjaToast; 9 | 10 | public class ExportWhitelistTask extends AsyncTask { 11 | private Context context; 12 | private ProgressDialog dialog; 13 | private String path; 14 | 15 | public ExportWhitelistTask(Context context) { 16 | this.context = context; 17 | this.dialog = null; 18 | this.path = null; 19 | } 20 | 21 | @Override 22 | protected void onPreExecute() { 23 | dialog = new ProgressDialog(context); 24 | dialog.setCancelable(false); 25 | dialog.setMessage(context.getString(R.string.toast_wait_a_minute)); 26 | dialog.show(); 27 | } 28 | 29 | @Override 30 | protected Boolean doInBackground(Void... params) { 31 | path = BrowserUnit.exportWhitelist(context); 32 | 33 | if (isCancelled()) { 34 | return false; 35 | } 36 | return path != null && !path.isEmpty(); 37 | } 38 | 39 | @Override 40 | protected void onPostExecute(Boolean result) { 41 | dialog.hide(); 42 | dialog.dismiss(); 43 | 44 | if (result) { 45 | NinjaToast.show(context, context.getString(R.string.toast_export_whitelist_successful) + path); 46 | } else { 47 | NinjaToast.show(context, R.string.toast_export_whitelist_failed); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Ninja/res/layout/dialog_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 20 | 21 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Ninja/res/xml/preference_clear.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/NinjaDownloadListener.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.webkit.DownloadListener; 8 | import android.webkit.URLUtil; 9 | import io.github.mthli.Ninja.R; 10 | import io.github.mthli.Ninja.Unit.BrowserUnit; 11 | import io.github.mthli.Ninja.Unit.IntentUnit; 12 | 13 | public class NinjaDownloadListener implements DownloadListener { 14 | private Context context; 15 | 16 | public NinjaDownloadListener(Context context) { 17 | super(); 18 | this.context = context; 19 | } 20 | 21 | @Override 22 | public void onDownloadStart(final String url, String userAgent, final String contentDisposition, final String mimeType, long contentLength) { 23 | final Context holder = IntentUnit.getContext(); 24 | if (holder == null || !(holder instanceof Activity)) { 25 | BrowserUnit.download(context, url, contentDisposition, mimeType); 26 | return; 27 | } 28 | 29 | AlertDialog.Builder builder = new AlertDialog.Builder(holder); 30 | builder.setCancelable(false); 31 | 32 | builder.setTitle(R.string.dialog_title_download); 33 | builder.setMessage(URLUtil.guessFileName(url, contentDisposition, mimeType)); 34 | 35 | builder.setPositiveButton(R.string.dialog_button_positive, new DialogInterface.OnClickListener() { 36 | @Override 37 | public void onClick(DialogInterface dialog, int which) { 38 | BrowserUnit.download(holder, url, contentDisposition, mimeType); 39 | } 40 | }); 41 | 42 | builder.setNegativeButton(R.string.dialog_button_negative, null); 43 | builder.create().show(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Task/ImportBookmarksTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Task; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.os.AsyncTask; 6 | import io.github.mthli.Ninja.Fragment.SettingFragment; 7 | import io.github.mthli.Ninja.R; 8 | import io.github.mthli.Ninja.Unit.BrowserUnit; 9 | import io.github.mthli.Ninja.View.NinjaToast; 10 | 11 | import java.io.File; 12 | 13 | public class ImportBookmarksTask extends AsyncTask { 14 | private SettingFragment fragment; 15 | private Context context; 16 | private ProgressDialog dialog; 17 | private File file; 18 | private int count; 19 | 20 | public ImportBookmarksTask(SettingFragment fragment, File file) { 21 | this.fragment = fragment; 22 | this.context = fragment.getActivity(); 23 | this.dialog = null; 24 | this.file = file; 25 | this.count = 0; 26 | } 27 | 28 | @Override 29 | protected void onPreExecute() { 30 | dialog = new ProgressDialog(context); 31 | dialog.setCancelable(false); 32 | dialog.setMessage(context.getString(R.string.toast_wait_a_minute)); 33 | dialog.show(); 34 | } 35 | 36 | @Override 37 | protected Boolean doInBackground(Void... params) { 38 | count = BrowserUnit.importBookmarks(context, file); 39 | 40 | if (isCancelled()) { 41 | return false; 42 | } 43 | return count >= 0; 44 | } 45 | 46 | @Override 47 | protected void onPostExecute(Boolean result) { 48 | dialog.hide(); 49 | dialog.dismiss(); 50 | 51 | if (result) { 52 | fragment.setDBChange(true); 53 | NinjaToast.show(context, context.getString(R.string.toast_import_bookmarks_successful) + count); 54 | } else { 55 | NinjaToast.show(context, R.string.toast_import_bookmarks_failed); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Task/ImportWhitelistTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Task; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.os.AsyncTask; 6 | import io.github.mthli.Ninja.Fragment.SettingFragment; 7 | import io.github.mthli.Ninja.R; 8 | import io.github.mthli.Ninja.Unit.BrowserUnit; 9 | import io.github.mthli.Ninja.View.NinjaToast; 10 | 11 | import java.io.File; 12 | 13 | public class ImportWhitelistTask extends AsyncTask { 14 | private SettingFragment fragment; 15 | private Context context; 16 | private ProgressDialog dialog; 17 | private File file; 18 | private int count; 19 | 20 | public ImportWhitelistTask(SettingFragment fragment, File file) { 21 | this.fragment = fragment; 22 | this.context = fragment.getActivity(); 23 | this.dialog = null; 24 | this.file = file; 25 | this.count = 0; 26 | } 27 | 28 | @Override 29 | protected void onPreExecute() { 30 | dialog = new ProgressDialog(context); 31 | dialog.setCancelable(false); 32 | dialog.setMessage(context.getString(R.string.toast_wait_a_minute)); 33 | dialog.show(); 34 | } 35 | 36 | @Override 37 | protected Boolean doInBackground(Void... params) { 38 | count = BrowserUnit.importWhitelist(context, file); 39 | 40 | if (isCancelled()) { 41 | return false; 42 | } 43 | return count >= 0; 44 | } 45 | 46 | @Override 47 | protected void onPostExecute(Boolean result) { 48 | dialog.hide(); 49 | dialog.dismiss(); 50 | 51 | if (result) { 52 | fragment.setDBChange(true); 53 | NinjaToast.show(context, context.getString(R.string.toast_import_whitelist_successful) + count); 54 | } else { 55 | NinjaToast.show(context, R.string.toast_import_whitelist_failed); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/RecordAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.TextView; 9 | import com.github.curioustechizen.ago.RelativeTimeTextView; 10 | import io.github.mthli.Ninja.Database.Record; 11 | import io.github.mthli.Ninja.R; 12 | 13 | import java.util.List; 14 | 15 | public class RecordAdapter extends ArrayAdapter { 16 | private Context context; 17 | private int layoutResId; 18 | private List list; 19 | 20 | public RecordAdapter(Context context, int layoutResId, List list) { 21 | super(context, layoutResId, list); 22 | this.context = context; 23 | this.layoutResId = layoutResId; 24 | this.list = list; 25 | } 26 | 27 | private static class Holder { 28 | TextView title; 29 | RelativeTimeTextView time; 30 | TextView url; 31 | } 32 | 33 | @Override 34 | public View getView(int position, View convertView, ViewGroup parent) { 35 | Holder holder; 36 | View view = convertView; 37 | 38 | if (view == null) { 39 | view = LayoutInflater.from(context).inflate(layoutResId, parent, false); 40 | holder = new Holder(); 41 | holder.title = (TextView) view.findViewById(R.id.record_item_title); 42 | holder.time = (RelativeTimeTextView) view.findViewById(R.id.record_item_time); 43 | holder.url = (TextView) view.findViewById(R.id.record_item_url); 44 | view.setTag(holder); 45 | } else { 46 | holder = (Holder) view.getTag(); 47 | } 48 | 49 | Record record = list.get(position); 50 | holder.title.setText(record.getTitle()); 51 | holder.time.setReferenceTime(record.getTime()); 52 | holder.url.setText(record.getURL()); 53 | 54 | return view; 55 | } 56 | } -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/GridAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | import io.github.mthli.Ninja.R; 10 | import io.github.mthli.Ninja.Unit.BrowserUnit; 11 | import io.github.mthli.Ninja.Unit.ViewUnit; 12 | import org.askerov.dynamicgrid.BaseDynamicGridAdapter; 13 | 14 | import java.util.List; 15 | 16 | public class GridAdapter extends BaseDynamicGridAdapter { 17 | private static class Holder { 18 | TextView title; 19 | ImageView cover; 20 | } 21 | 22 | private List list; 23 | public List getList() { 24 | return list; 25 | } 26 | 27 | private Context context; 28 | 29 | public GridAdapter(Context context, List list, int columnCount) { 30 | super(context, list, columnCount); 31 | this.context = context; 32 | this.list = list; 33 | } 34 | 35 | @Override 36 | public View getView(int position, View convertView, ViewGroup parent) { 37 | Holder holder; 38 | View view = convertView; 39 | 40 | if (view == null) { 41 | view = LayoutInflater.from(context).inflate(R.layout.grid_item, parent, false); 42 | holder = new Holder(); 43 | holder.title = (TextView) view.findViewById(R.id.grid_item_title); 44 | holder.cover = (ImageView) view.findViewById(R.id.grid_item_cover); 45 | view.setTag(holder); 46 | } else { 47 | holder = (Holder) view.getTag(); 48 | } 49 | 50 | GridItem item = list.get(position); 51 | holder.title.setText(item.getTitle()); 52 | holder.cover.setImageBitmap(BrowserUnit.file2Bitmap(context, item.getFilename())); 53 | ViewUnit.setElevation(view, context.getResources().getDimensionPixelSize(R.dimen.elevation_1dp)); 54 | 55 | return view; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/BrowserContainer.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import io.github.mthli.Ninja.View.NinjaWebView; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | public class BrowserContainer { 9 | private static List list = new LinkedList<>(); 10 | 11 | public static AlbumController get(int index) { 12 | return list.get(index); 13 | } 14 | 15 | public synchronized static void set(AlbumController controller, int index) { 16 | if (list.get(index) instanceof NinjaWebView) { 17 | ((NinjaWebView) list.get(index)).destroy(); 18 | } 19 | 20 | list.set(index, controller); 21 | } 22 | 23 | public synchronized static void add(AlbumController controller) { 24 | list.add(controller); 25 | } 26 | 27 | public synchronized static void add(AlbumController controller, int index) { 28 | list.add(index, controller); 29 | } 30 | 31 | public synchronized static void remove(int index) { 32 | if (list.get(index) instanceof NinjaWebView) { 33 | ((NinjaWebView) list.get(index)).destroy(); 34 | } 35 | 36 | list.remove(index); 37 | } 38 | 39 | public synchronized static void remove(AlbumController controller) { 40 | if (controller instanceof NinjaWebView) { 41 | ((NinjaWebView) controller).destroy(); 42 | } 43 | 44 | list.remove(controller); 45 | } 46 | 47 | public static int indexOf(AlbumController controller) { 48 | return list.indexOf(controller); 49 | } 50 | 51 | public static List list() { 52 | return list; 53 | } 54 | 55 | public static int size() { 56 | return list.size(); 57 | } 58 | 59 | public synchronized static void clear() { 60 | for (AlbumController albumController : list) { 61 | if (albumController instanceof NinjaWebView) { 62 | ((NinjaWebView) albumController).destroy(); 63 | } 64 | } 65 | 66 | list.clear(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Service/ClearService.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Service; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.os.IBinder; 7 | import android.preference.PreferenceManager; 8 | import io.github.mthli.Ninja.R; 9 | import io.github.mthli.Ninja.Unit.BrowserUnit; 10 | 11 | public class ClearService extends Service { 12 | @Override 13 | public IBinder onBind(Intent intent) { 14 | return null; 15 | } 16 | 17 | @Override 18 | public void onDestroy() { 19 | System.exit(0); // For remove all WebView thread 20 | } 21 | 22 | @Override 23 | public int onStartCommand(Intent intent, int flags, int startId) { 24 | clear(); 25 | stopSelf(); 26 | return START_STICKY; 27 | } 28 | 29 | private void clear() { 30 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 31 | boolean clearBookmarks = sp.getBoolean(getString(R.string.sp_clear_bookmarks), false); 32 | boolean clearCache = sp.getBoolean(getString(R.string.sp_clear_cache), true); 33 | boolean clearCookie = sp.getBoolean(getString(R.string.sp_clear_cookie), false); 34 | boolean clearFormData = sp.getBoolean(getString(R.string.sp_clear_form_data), false); 35 | boolean clearHistory = sp.getBoolean(getString(R.string.sp_clear_history), true); 36 | boolean clearPasswords = sp.getBoolean(getString(R.string.sp_clear_passwords), false); 37 | 38 | if (clearBookmarks) { 39 | BrowserUnit.clearBookmarks(this); 40 | } 41 | if (clearCache) { 42 | BrowserUnit.clearCache(this); 43 | } 44 | if (clearCookie) { 45 | BrowserUnit.clearCookie(this); 46 | } 47 | if (clearFormData) { 48 | BrowserUnit.clearFormData(this); 49 | } 50 | if (clearHistory) { 51 | BrowserUnit.clearHistory(this); 52 | } 53 | if (clearPasswords) { 54 | BrowserUnit.clearPasswords(this); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Ninja/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16dp 7 | 8 | 210dp 9 | 156dp 10 | 144dp 11 | 12 | 320dp 13 | 141dp 14 | 117dp 15 | 108dp 16 | 48dp 17 | 36dp 18 | 32dp 19 | 25dp 20 | 12dp 21 | 6dp 22 | 2dp 23 | 1dp 24 | 25 | 48dp 26 | 20dp 27 | 16dp 28 | 14dp 29 | 12dp 30 | 8dp 31 | 4dp 32 | 2dp 33 | 34 | 16dp 35 | 8dp 36 | 4dp 37 | 38 | 18sp 39 | 16sp 40 | 14sp 41 | 12sp 42 | 43 | 1dp 44 | 0dp 45 | 2dp 46 | 1dp 47 | 0dp 48 | 2dp 49 | 1dp 50 | 51 | 52 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Unit/RecordUnit.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Unit; 2 | 3 | import io.github.mthli.Ninja.Database.Record; 4 | 5 | public class RecordUnit { 6 | public static final String TABLE_BOOKMARKS = "BOOKMARKS"; 7 | public static final String TABLE_HISTORY = "HISTORY"; 8 | public static final String TABLE_WHITELIST = "WHITELIST"; 9 | public static final String TABLE_GRID = "GRID"; 10 | 11 | public static final String COLUMN_TITLE = "TITLE"; 12 | public static final String COLUMN_URL = "URL"; 13 | public static final String COLUMN_TIME = "TIME"; 14 | public static final String COLUMN_DOMAIN = "DOMAIN"; 15 | public static final String COLUMN_FILENAME = "FILENAME"; 16 | public static final String COLUMN_ORDINAL = "ORDINAL"; 17 | 18 | public static final String CREATE_HISTORY = "CREATE TABLE " 19 | + TABLE_HISTORY 20 | + " (" 21 | + " " + COLUMN_TITLE + " text," 22 | + " " + COLUMN_URL + " text," 23 | + " " + COLUMN_TIME + " integer" 24 | + ")"; 25 | 26 | public static final String CREATE_BOOKMARKS = "CREATE TABLE " 27 | + TABLE_BOOKMARKS 28 | + " (" 29 | + " " + COLUMN_TITLE + " text," 30 | + " " + COLUMN_URL + " text," 31 | + " " + COLUMN_TIME + " integer" 32 | + ")"; 33 | 34 | public static final String CREATE_WHITELIST = "CREATE TABLE " 35 | + TABLE_WHITELIST 36 | + " (" 37 | + " " + COLUMN_DOMAIN + " text" 38 | + ")"; 39 | 40 | public static final String CREATE_GRID = "CREATE TABLE " 41 | + TABLE_GRID 42 | + " (" 43 | + " " + COLUMN_TITLE + " text," 44 | + " " + COLUMN_URL + " text," 45 | + " " + COLUMN_FILENAME + " text," 46 | + " " + COLUMN_ORDINAL + " integer" 47 | + ")"; 48 | 49 | private static Record holder; 50 | public static Record getHolder() { 51 | return holder; 52 | } 53 | public synchronized static void setHolder(Record record) { 54 | holder = record; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/WhitelistAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.ImageButton; 9 | import android.widget.TextView; 10 | import io.github.mthli.Ninja.Browser.AdBlock; 11 | import io.github.mthli.Ninja.R; 12 | 13 | import java.util.List; 14 | 15 | public class WhitelistAdapter extends ArrayAdapter { 16 | private Context context; 17 | private int layoutResId; 18 | private List list; 19 | 20 | public WhitelistAdapter(Context context, int layoutResId, List list){ 21 | super(context, layoutResId, list); 22 | this.context = context; 23 | this.layoutResId = layoutResId; 24 | this.list = list; 25 | } 26 | 27 | private static class Holder { 28 | TextView domain; 29 | ImageButton cancel; 30 | } 31 | 32 | @Override 33 | public View getView(final int position, View convertView, ViewGroup parent) { 34 | Holder holder; 35 | View view = convertView; 36 | 37 | if (view == null) { 38 | view = LayoutInflater.from(context).inflate(layoutResId, parent, false); 39 | holder = new Holder(); 40 | holder.domain = (TextView) view.findViewById(R.id.whilelist_item_domain); 41 | holder.cancel = (ImageButton) view.findViewById(R.id.whilelist_item_cancel); 42 | view.setTag(holder); 43 | } else { 44 | holder = (Holder) view.getTag(); 45 | } 46 | 47 | holder.domain.setText(list.get(position)); 48 | holder.cancel.setOnClickListener(new View.OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | AdBlock adBlock = new AdBlock(context); 52 | adBlock.removeDomain(list.get(position)); 53 | list.remove(position); 54 | notifyDataSetChanged(); 55 | NinjaToast.show(context, R.string.toast_delete_successful); 56 | } 57 | }); 58 | 59 | return view; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Ninja/src/org/askerov/dynamicgrid/AbstractDynamicGridAdapter.java: -------------------------------------------------------------------------------- 1 | package org.askerov.dynamicgrid; 2 | 3 | import android.widget.BaseAdapter; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | 8 | /** 9 | * Author: alex askerov 10 | * Date: 9/6/13 11 | * Time: 7:43 PM 12 | */ 13 | 14 | 15 | /** 16 | * Abstract adapter for {@link org.askerov.dynamicgrid.DynamicGridView} with sable items id; 17 | */ 18 | 19 | public abstract class AbstractDynamicGridAdapter extends BaseAdapter implements DynamicGridAdapterInterface { 20 | public static final int INVALID_ID = -1; 21 | 22 | private int nextStableId = 0; 23 | 24 | private HashMap mIdMap = new HashMap(); 25 | 26 | /** 27 | * Adapter must have stable id 28 | * 29 | * @return 30 | */ 31 | @Override 32 | public final boolean hasStableIds() { 33 | return true; 34 | } 35 | 36 | /** 37 | * creates stable id for object 38 | * 39 | * @param item 40 | */ 41 | protected void addStableId(Object item) { 42 | mIdMap.put(item, nextStableId++); 43 | } 44 | 45 | /** 46 | * create stable ids for list 47 | * 48 | * @param items 49 | */ 50 | protected void addAllStableId(List items) { 51 | for (Object item : items) { 52 | addStableId(item); 53 | } 54 | } 55 | 56 | /** 57 | * get id for position 58 | * 59 | * @param position 60 | * @return 61 | */ 62 | @Override 63 | public final long getItemId(int position) { 64 | if (position < 0 || position >= mIdMap.size()) { 65 | return INVALID_ID; 66 | } 67 | Object item = getItem(position); 68 | return mIdMap.get(item); 69 | } 70 | 71 | /** 72 | * clear stable id map 73 | * should called when clear adapter data; 74 | */ 75 | protected void clearStableIdMap() { 76 | mIdMap.clear(); 77 | } 78 | 79 | /** 80 | * remove stable id for item. Should called on remove data item from adapter 81 | * 82 | * @param item 83 | */ 84 | protected void removeStableID(Object item) { 85 | mIdMap.remove(item); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Ninja/res/layout/dialog_license_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 18 | 19 | 20 | 27 | 28 | 29 | 36 | 37 | 38 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Ninja/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 18 | 19 | 25 | 26 | 32 | 33 | 39 | 40 | 46 | 47 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Task/ScreenshotTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Task; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.os.AsyncTask; 7 | import io.github.mthli.Ninja.R; 8 | import io.github.mthli.Ninja.Unit.BrowserUnit; 9 | import io.github.mthli.Ninja.Unit.ViewUnit; 10 | import io.github.mthli.Ninja.View.NinjaToast; 11 | import io.github.mthli.Ninja.View.NinjaWebView; 12 | 13 | public class ScreenshotTask extends AsyncTask { 14 | private Context context; 15 | private ProgressDialog dialog; 16 | private NinjaWebView webView; 17 | private int windowWidth; 18 | private float contentHeight; 19 | private String title; 20 | private String path; 21 | 22 | public ScreenshotTask(Context context, NinjaWebView webView) { 23 | this.context = context; 24 | this.dialog = null; 25 | this.webView = webView; 26 | this.windowWidth = 0; 27 | this.contentHeight = 0f; 28 | this.title = null; 29 | this.path = null; 30 | } 31 | 32 | @Override 33 | protected void onPreExecute() { 34 | dialog = new ProgressDialog(context); 35 | dialog.setCancelable(false); 36 | dialog.setMessage(context.getString(R.string.toast_wait_a_minute)); 37 | dialog.show(); 38 | 39 | windowWidth = ViewUnit.getWindowWidth(context); 40 | contentHeight = webView.getContentHeight() * ViewUnit.getDensity(context); 41 | title = webView.getTitle(); 42 | } 43 | 44 | @Override 45 | protected Boolean doInBackground(Void... params) { 46 | try { 47 | Bitmap bitmap = ViewUnit.capture(webView, windowWidth, contentHeight, false, Bitmap.Config.ARGB_8888); 48 | path = BrowserUnit.screenshot(context, bitmap, title); 49 | } catch (Exception e) { 50 | path = null; 51 | } 52 | return path != null && !path.isEmpty(); 53 | } 54 | 55 | @Override 56 | protected void onPostExecute(Boolean result) { 57 | dialog.hide(); 58 | dialog.dismiss(); 59 | 60 | if (result) { 61 | NinjaToast.show(context, context.getString(R.string.toast_screenshot_successful) + path); 62 | } else { 63 | NinjaToast.show(context, R.string.toast_screenshot_failed); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Unit/NotificationUnit.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Unit; 2 | 3 | import android.app.Notification; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.os.Build; 9 | import android.preference.PreferenceManager; 10 | import io.github.mthli.Ninja.Activity.BrowserActivity; 11 | import io.github.mthli.Ninja.Browser.AlbumController; 12 | import io.github.mthli.Ninja.Browser.BrowserContainer; 13 | import io.github.mthli.Ninja.R; 14 | import io.github.mthli.Ninja.View.NinjaWebView; 15 | 16 | public class NotificationUnit { 17 | public static final int HOLDER_ID = 0x65536; 18 | 19 | public static Notification.Builder getHBuilder(Context context) { 20 | Notification.Builder builder = new Notification.Builder(context); 21 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); 22 | 23 | int priority = Integer.valueOf(sp.getString(context.getString(R.string.sp_notification_priority), "0")); 24 | if (priority == 0) { 25 | builder.setPriority(Notification.PRIORITY_DEFAULT); 26 | } else if (priority == 1) { 27 | builder.setPriority(Notification.PRIORITY_HIGH); 28 | } else if (priority == 2) { 29 | builder.setPriority(Notification.PRIORITY_LOW); 30 | } else { 31 | builder.setPriority(Notification.PRIORITY_DEFAULT); 32 | } 33 | 34 | int total = 0; 35 | for (AlbumController controller : BrowserContainer.list()) { 36 | if (controller instanceof NinjaWebView) { 37 | total++; 38 | } 39 | } 40 | builder.setNumber(total); 41 | 42 | builder.setSmallIcon(R.drawable.ic_notification_ninja); 43 | builder.setContentTitle(context.getString(R.string.app_name)); 44 | builder.setContentText(context.getString(R.string.notification_content_holder)); 45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 46 | builder.setColor(context.getResources().getColor(R.color.blue_500)); 47 | } 48 | 49 | Intent toActivity = new Intent(context, BrowserActivity.class); 50 | PendingIntent pin = PendingIntent.getActivity(context, 0, toActivity, 0); 51 | builder.setContentIntent(pin); 52 | 53 | return builder; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/NinjaRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.RelativeLayout; 8 | import io.github.mthli.Ninja.Browser.AlbumController; 9 | import io.github.mthli.Ninja.Browser.BrowserController; 10 | import io.github.mthli.Ninja.R; 11 | 12 | public class NinjaRelativeLayout extends RelativeLayout implements AlbumController { 13 | private Context context; 14 | private Album album; 15 | private int flag = 0; 16 | 17 | private BrowserController controller; 18 | public void setBrowserController(BrowserController controller) { 19 | this.controller = controller; 20 | this.album.setBrowserController(controller); 21 | } 22 | 23 | public NinjaRelativeLayout(Context context) { 24 | this(context, null); 25 | } 26 | 27 | public NinjaRelativeLayout(Context context, AttributeSet attrs) { 28 | this(context, attrs, 0); 29 | } 30 | 31 | public NinjaRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | this.context = context; 34 | this.album = new Album(context, this, this.controller); 35 | initUI(); 36 | } 37 | 38 | private void initUI() { 39 | album.setAlbumCover(null); 40 | album.setAlbumTitle(context.getString(R.string.album_untitled)); 41 | album.setBrowserController(controller); 42 | } 43 | 44 | @Override 45 | public int getFlag() { 46 | return flag; 47 | } 48 | 49 | @Override 50 | public void setFlag(int flag) { 51 | this.flag = flag; 52 | } 53 | 54 | @Override 55 | public View getAlbumView() { 56 | return album.getAlbumView(); 57 | } 58 | 59 | @Override 60 | public void setAlbumCover(Bitmap bitmap) { 61 | album.setAlbumCover(bitmap); 62 | } 63 | 64 | @Override 65 | public String getAlbumTitle() { 66 | return album.getAlbumTitle(); 67 | } 68 | 69 | @Override 70 | public void setAlbumTitle(String title) { 71 | album.setAlbumTitle(title); 72 | } 73 | 74 | @Override 75 | public void activate() { 76 | album.activate(); 77 | } 78 | 79 | @Override 80 | public void deactivate() { 81 | album.deactivate(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Ninja/assets/ninja_introduction_en.md: -------------------------------------------------------------------------------- 1 | Ninja introduction 2 | === 3 | 4 | __The author has to prepare for the final exam, so MAYBE pause updates__. 5 | 6 | Basically Ninja is a simple web browser like any other, but there are some different things you need to know: 7 | 8 | ### Custom home: 9 | 10 | - On the applications first start, home page shows as `about:blank`. 11 | 12 | - Use the overflow menu's __Add to home__ to pin webpages to Homepage. 13 | 14 | - Use the overflow menu's __Relayout__ to customize your homepage layout. 15 | 16 | - __Long press__ on a card on the homepage, and you can edit the title. 17 | 18 | ## Switch tabs: 19 | 20 | - You can set tab switcher position to be at the __screen top or screen bottom__ at `Settings/Browser/Omnibox Position`. 21 | 22 | - Press the __address bar__ and __drag it down or up__, then the fashion tab switcher will display. 23 | 24 | - __Swipe up/down__ to dimiss a page. 25 | 26 | - Or just __swipe the omnibox left/right__ to swicth tabs :) 27 | 28 | The __Settings__ is located in the top or bottom left of tab switcher, the __gear__ icon. 29 | 30 | __Remember__ that if the soft keyboard is shown the tab switcher would not display, it's our design :) 31 | 32 | ## Load in background when you click links in other App: 33 | 34 | 1. Set Ninja as your __default browser__ when the link is clicked. 35 | 36 | 2. __Single tap__ will open the links in the background, and show a clickable notification in the statusbar. 37 | 38 | 3. __Double tap__ will show a dialog that allows you to open links in foreground, etc. 39 | 40 | ## Volume control: 41 | 42 | In `Settings/Browser/Volume control` you can set the volume keys behavior to: 43 | 44 | - Switch tabs. 45 | 46 | - Scroll webpages. 47 | 48 | - System default. 49 | 50 | ## AdBlock whitelist: 51 | 52 | Since AdBlock maybe cause some websites display error, you can add those websites to the whitelist `Settings/AdBlock/Whitelist`. 53 | 54 | ## Readability: 55 | 56 | Ninja supports read mode but it needs __Readability API Token__. 57 | 58 | To get the token you should go to [Readability Developer APIs](https://www.readability.com/developers/api "Readability Developer APIs") and set it in `Settings/Readability/Token`. 59 | 60 | ## Screenshot: 61 | 62 | Ninja can __capture entire webpages__! 63 | 64 | But that does not mean you could screenshot a very long webpage which may cause the browser to go __Out of Memory__. 65 | 66 | --- 67 | 68 | _2015.06.19 Matthew Lee_ 69 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Task/ReadabilityTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Task; 2 | 3 | import android.os.AsyncTask; 4 | import io.github.mthli.Ninja.Activity.ReadabilityActivity; 5 | import org.json.JSONObject; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.InputStreamReader; 9 | import java.net.HttpURLConnection; 10 | import java.net.URL; 11 | 12 | public class ReadabilityTask extends AsyncTask { 13 | private String query = null; 14 | private JSONObject result = null; 15 | 16 | private ReadabilityActivity activity; 17 | 18 | public ReadabilityTask(ReadabilityActivity activity, String query) { 19 | this.activity = activity; 20 | this.query = query; 21 | } 22 | 23 | @Override 24 | protected void onPreExecute() { 25 | activity.setStatus(ReadabilityActivity.Status.RUNNING); 26 | } 27 | 28 | @Override 29 | protected Boolean doInBackground(Void... params) { 30 | try { 31 | URL url = new URL(query); 32 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 33 | connection.setDefaultUseCaches(true); 34 | connection.setUseCaches(true); 35 | connection.connect(); 36 | 37 | if (connection.getResponseCode() == 200) { 38 | BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 39 | StringBuilder builder = new StringBuilder(); 40 | String line; 41 | while ((line = reader.readLine()) != null) { 42 | builder.append(line); 43 | } 44 | reader.close(); 45 | connection.disconnect(); 46 | 47 | result = new JSONObject(builder.toString()); 48 | } else { 49 | result = null; 50 | } 51 | } catch (Exception e) { 52 | result = null; 53 | } 54 | 55 | if (isCancelled()) { 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | @Override 62 | protected void onPostExecute(Boolean b) { 63 | activity.setStatus(ReadabilityActivity.Status.IDLE); 64 | if (b) { 65 | activity.setResult(result); 66 | activity.showLoadSuccessful(); 67 | } else { 68 | activity.setResult(null); 69 | activity.showLoadError(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Unit/IntentUnit.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Unit; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.MailTo; 6 | 7 | public class IntentUnit { 8 | public static final String OPEN = "OPEN"; 9 | public static final String URL = "URL"; 10 | 11 | public static final int REQUEST_BOOKMARKS = 0x100; 12 | public static final int REQUEST_FILE_16 = 0x101; 13 | public static final int REQUEST_FILE_21 = 0x102; 14 | public static final int REQUEST_WHITELIST = 0x103; 15 | public static final int REQUEST_CLEAR = 0x104; 16 | public static final String INTENT_TYPE_TEXT_PLAIN = "text/plain"; 17 | public static final String INTENT_TYPE_MESSAGE_RFC822 = "message/rfc822"; 18 | 19 | public static Intent getEmailIntent(MailTo mailTo) { 20 | Intent intent = new Intent(Intent.ACTION_SEND); 21 | intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mailTo.getTo() }); 22 | intent.putExtra(Intent.EXTRA_TEXT, mailTo.getBody()); 23 | intent.putExtra(Intent.EXTRA_SUBJECT, mailTo.getSubject()); 24 | intent.putExtra(Intent.EXTRA_CC, mailTo.getCc()); 25 | intent.setType(INTENT_TYPE_MESSAGE_RFC822); 26 | 27 | return intent; 28 | } 29 | 30 | public static void share(Context context, String title, String url) { 31 | Intent intent = new Intent(Intent.ACTION_SEND); 32 | intent.setType(INTENT_TYPE_TEXT_PLAIN); 33 | intent.putExtra(Intent.EXTRA_TEXT, title + "\n" + url); 34 | context.startActivity(intent); 35 | } 36 | 37 | // Activity holder 38 | private static Context context = null; 39 | public static void setContext(Context holder) { 40 | context = holder; 41 | } 42 | public static Context getContext() { 43 | return context; 44 | } 45 | 46 | private static boolean clear = false; 47 | public static boolean isClear() { 48 | return clear; 49 | } 50 | public synchronized static void setClear(boolean b) { 51 | clear = b; 52 | } 53 | 54 | private static boolean dbChange = false; 55 | public static boolean isDBChange() { 56 | return dbChange; 57 | } 58 | public static void setDBChange(boolean b) { 59 | dbChange = b; 60 | } 61 | 62 | private static boolean spChange = false; 63 | public static boolean isSPChange() { 64 | return spChange; 65 | } 66 | public static void setSPChange(boolean b) { 67 | spChange = b; 68 | } 69 | } -------------------------------------------------------------------------------- /Ninja/res/layout/token.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 26 | 27 | 28 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Ninja/res/layout/record_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 25 | 26 | 27 | 38 | 39 | 40 | 41 | 42 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Ninja/assets/ninja_introduction_zh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | Ninja使用指南 22 | 23 |
24 | 25 |

Ninja使用指南

26 | 27 |

作者要准备期末考试了,所以可能暂停更新

28 | 29 |

Ninja在操作上与其他浏览器并没有明显的区别,但是仍然有以下值得注意的地方:

30 | 31 |

自定义主页:

32 | 33 |
  • 当主页上没有添加任何东西时,主页将显示为about:blank

  • 当你在浏览网页时,点击地址栏的菜单按钮,选择添加拨号即可将当前浏览网页添加到主页中。

  • 当你在主页时,点击地址栏的菜单按钮,选择排版即可对主页上的拨号卡片进行排序长按拨号卡片则可对其进行标题编辑等。

34 | 35 |

切换标签页:

36 | 37 |
  • 你可以在设置/浏览/地址栏位置中设置地址栏显示在屏幕顶部屏幕底部

  • 按住地址栏向下拖拽向上拖拽即可显示标签页切换器。

  • 上下滑动标签关闭网页。

  • 你也可以左右滑动地址栏来切换标签页。

38 | 39 |

地址栏顶部或底部左侧的齿轮图标即为设置选项。

40 | 41 |

注意,当软键盘处于展开状态时,标签页切换器将不会工作以防止误操作。

42 | 43 |

在其他App中点击链接实现后台加载:

44 | 45 |
  1. 在其他App中点击链接时请将Ninja设置为默认浏览器

  2. 单击链接Ninja将自动在后台加载,并在通知栏显示相应状态。

  3. 双击链接Ninja将会弹出对话框,你可以选择跳转到前台加载或选择其他操作。

46 | 47 |

音量键控制:

48 | 49 |

设置/浏览/音量键控制中你可以设定音量键的行为:

50 | 51 |
  • 切换标签页。

  • 滚动网页。

  • 系统默认的音量控制。

52 | 53 |

AdBlock白名单:

54 | 55 |

开启AdBlock功能以后,有一些网站可能会因为AdBlock而无法正常地显示内容。

56 | 57 |

你可以在设置/Adblock/白名单中将这些网站加入AdBlock的白名单。

58 | 59 |

阅读模式:

60 | 61 |

Ninja支持阅读模式,但是实现这个功能需要Readability的令牌

62 | 63 |

你可以前往Readability Developer APIs获取你自己的令牌并在设置/阅读模式/口令中设置。

64 | 65 |

网页截图:

66 | 67 |

Ninja提供了网页截图功能,可以截取整张网页。

68 | 69 |

但是这并不意味着你可以截取相当相当相当长的网页而不出现问题,可能会因为OOM等原因而崩溃。

70 | 71 |
72 | 73 |

2015.06.19 Matthew Lee

74 | 75 |
-------------------------------------------------------------------------------- /Ninja/res/values/strings_key.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SP_FIRST_26 7 | 8 | 9 | 10 | 11 | SP_AD_BLOCK_9 12 | 13 | 14 | SP_ANCHOR_9 15 | 16 | 17 | SP_COOKIES_9 18 | 19 | 20 | SP_IMAGES_9 21 | 22 | 23 | SP_JAVASCRIPT_9 24 | 25 | 26 | SP_LOCATION_9 27 | 28 | 29 | SP_MULTIPLE_WINDOWS_9 30 | 31 | 32 | SP_NOTIFICATION_PRIORITY_9 33 | 34 | 35 | SP_OMNIBOX_CONTROL_9 36 | 37 | 38 | SP_PASSWORDS_9 39 | 40 | 41 | SP_RENDERING_9 42 | 43 | 44 | SP_SEARCH_ENGINE_9 45 | 46 | 47 | SP_SEARCH_ENGINE_CUSTOM_9 48 | 49 | 50 | SP_TEXT_REFLOW_9 51 | 52 | 53 | SP_USER_AGENT_9 54 | 55 | 56 | SP_USER_AGENT_CUSTOM_9 57 | 58 | 59 | SP_SCROLL_BAR_9 60 | 61 | 62 | SP_VOLUME_9 63 | 64 | 65 | 66 | 67 | SP_CLEAR_QUIT_9 68 | 69 | 70 | SP_CLEAR_BOOKMARKS_9 71 | 72 | 73 | SP_CLEAR_CACHE_9 74 | 75 | 76 | SP_CLEAR_COOKIE_9 77 | 78 | 79 | SP_CLEAR_FORM_DATA_9 80 | 81 | 82 | SP_CLEAR_HISTORY_9 83 | 84 | 85 | SP_CLEAR_PASSWORDS_9 86 | 87 | 88 | 89 | 90 | SP_READABILITY_9 91 | 92 | 93 | SP_READABILITY_TOKEN_9 94 | 95 | 96 | SP_READABILITY_BACKGROUND_9 97 | 98 | 99 | -------------------------------------------------------------------------------- /Ninja/res/layout/whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 51 | 52 | 53 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Ninja/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | -------------------------------------------------------------------------------- /Ninja/src/org/askerov/dynamicgrid/BaseDynamicGridAdapter.java: -------------------------------------------------------------------------------- 1 | package org.askerov.dynamicgrid; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Author: alex askerov 10 | * Date: 9/7/13 11 | * Time: 10:49 PM 12 | */ 13 | public abstract class BaseDynamicGridAdapter extends AbstractDynamicGridAdapter { 14 | private Context mContext; 15 | 16 | private ArrayList mItems = new ArrayList(); 17 | private int mColumnCount; 18 | 19 | protected BaseDynamicGridAdapter(Context context, int columnCount) { 20 | this.mContext = context; 21 | this.mColumnCount = columnCount; 22 | } 23 | 24 | public BaseDynamicGridAdapter(Context context, List items, int columnCount) { 25 | mContext = context; 26 | mColumnCount = columnCount; 27 | init(items); 28 | } 29 | 30 | private void init(List items) { 31 | addAllStableId(items); 32 | this.mItems.addAll(items); 33 | } 34 | 35 | 36 | public void set(List items) { 37 | clear(); 38 | init(items); 39 | notifyDataSetChanged(); 40 | } 41 | 42 | public void clear() { 43 | clearStableIdMap(); 44 | mItems.clear(); 45 | notifyDataSetChanged(); 46 | } 47 | 48 | public void add(Object item) { 49 | addStableId(item); 50 | mItems.add(item); 51 | notifyDataSetChanged(); 52 | } 53 | 54 | public void add(int position, Object item) { 55 | addStableId(item); 56 | mItems.add(position, item); 57 | notifyDataSetChanged(); 58 | } 59 | 60 | public void add(List items) { 61 | addAllStableId(items); 62 | this.mItems.addAll(items); 63 | notifyDataSetChanged(); 64 | } 65 | 66 | 67 | public void remove(Object item) { 68 | mItems.remove(item); 69 | removeStableID(item); 70 | notifyDataSetChanged(); 71 | } 72 | 73 | 74 | @Override 75 | public int getCount() { 76 | return mItems.size(); 77 | } 78 | 79 | @Override 80 | public Object getItem(int position) { 81 | return mItems.get(position); 82 | } 83 | 84 | @Override 85 | public int getColumnCount() { 86 | return mColumnCount; 87 | } 88 | 89 | public void setColumnCount(int columnCount) { 90 | this.mColumnCount = columnCount; 91 | notifyDataSetChanged(); 92 | } 93 | 94 | @Override 95 | public void reorderItems(int originalPosition, int newPosition) { 96 | if (newPosition < getCount()) { 97 | DynamicGridUtils.reorder(mItems, originalPosition, newPosition); 98 | notifyDataSetChanged(); 99 | } 100 | } 101 | 102 | @Override 103 | public boolean canReorder(int position) { 104 | return true; 105 | } 106 | 107 | public List getItems() { 108 | return mItems; 109 | } 110 | 111 | protected Context getContext() { 112 | return mContext; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Activity/TokenActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.os.Bundle; 7 | import android.preference.PreferenceManager; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.view.inputmethod.InputMethodManager; 12 | import android.widget.Button; 13 | import android.widget.EditText; 14 | import io.github.mthli.Ninja.R; 15 | import io.github.mthli.Ninja.View.NinjaToast; 16 | 17 | public class TokenActivity extends Activity { 18 | @Override 19 | public void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.token); 22 | getActionBar().setDisplayHomeAsUpEnabled(true); 23 | 24 | final EditText tokenEdit = (EditText) findViewById(R.id.token_edit); 25 | Button tokenAdd = (Button) findViewById(R.id.token_add); 26 | 27 | final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 28 | String token = sp.getString(getString(R.string.sp_readability_token), ""); 29 | tokenEdit.setText(token); 30 | tokenEdit.setSelection(token.length()); 31 | showSoftInput(tokenEdit); 32 | 33 | tokenAdd.setOnClickListener(new View.OnClickListener() { 34 | @Override 35 | public void onClick(View v) { 36 | if (tokenEdit.getText().toString().trim().isEmpty()) { 37 | NinjaToast.show(TokenActivity.this, R.string.toast_input_empty); 38 | } else { 39 | sp.edit().putString( 40 | getString(R.string.sp_readability_token), 41 | tokenEdit.getText().toString().trim() 42 | ).commit(); 43 | NinjaToast.show(TokenActivity.this, R.string.toast_add_token_successful); 44 | } 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public void onPause() { 51 | hideSoftInput(findViewById(R.id.token_edit)); 52 | super.onPause(); 53 | } 54 | 55 | @Override 56 | public boolean onCreateOptionsMenu(Menu menu) { 57 | getMenuInflater().inflate(R.menu.token_menu, menu); 58 | return super.onCreateOptionsMenu(menu); 59 | } 60 | 61 | @Override 62 | public boolean onOptionsItemSelected(MenuItem menuItem) { 63 | switch (menuItem.getItemId()) { 64 | case android.R.id.home: 65 | finish(); 66 | break; 67 | default: 68 | break; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | private void hideSoftInput(View view) { 75 | view.clearFocus(); 76 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 77 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 78 | } 79 | 80 | private void showSoftInput(View view) { 81 | view.requestFocus(); 82 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 83 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Activity/SettingActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.KeyEvent; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import io.github.mthli.Ninja.Task.ImportBookmarksTask; 10 | import io.github.mthli.Ninja.Task.ImportWhitelistTask; 11 | import io.github.mthli.Ninja.View.NinjaToast; 12 | import io.github.mthli.Ninja.Fragment.SettingFragment; 13 | import io.github.mthli.Ninja.R; 14 | import io.github.mthli.Ninja.Unit.IntentUnit; 15 | 16 | import java.io.File; 17 | 18 | public class SettingActivity extends Activity { 19 | private SettingFragment fragment; 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | getActionBar().setDisplayHomeAsUpEnabled(true); 25 | 26 | fragment = new SettingFragment(); 27 | getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit(); 28 | } 29 | 30 | @Override 31 | public boolean onCreateOptionsMenu(Menu menu) { 32 | getMenuInflater().inflate(R.menu.setting_menu, menu); 33 | return super.onCreateOptionsMenu(menu); 34 | } 35 | 36 | @Override 37 | public boolean onOptionsItemSelected(MenuItem menuItem) { 38 | switch (menuItem.getItemId()) { 39 | case android.R.id.home: 40 | IntentUnit.setDBChange(fragment.isDBChange()); 41 | IntentUnit.setSPChange(fragment.isSPChange()); 42 | finish(); 43 | break; 44 | default: 45 | break; 46 | } 47 | 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { 53 | if (keyCode == KeyEvent.KEYCODE_BACK) { 54 | IntentUnit.setDBChange(fragment.isDBChange()); 55 | IntentUnit.setSPChange(fragment.isSPChange()); 56 | finish(); 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | @Override 64 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 65 | if (requestCode == IntentUnit.REQUEST_BOOKMARKS) { 66 | if (resultCode != Activity.RESULT_OK || data == null || data.getData() == null) { 67 | NinjaToast.show(this, R.string.toast_import_bookmarks_failed); 68 | } else { 69 | File file = new File(data.getData().getPath()); 70 | new ImportBookmarksTask(fragment, file).execute(); 71 | } 72 | } else if (requestCode == IntentUnit.REQUEST_WHITELIST) { 73 | if (resultCode != Activity.RESULT_OK || data == null || data.getData() == null) { 74 | NinjaToast.show(this, R.string.toast_import_whitelist_failed); 75 | } else { 76 | File file = new File(data.getData().getPath()); 77 | new ImportWhitelistTask(fragment, file).execute(); 78 | } 79 | } else if (requestCode == IntentUnit.REQUEST_CLEAR) { 80 | if (resultCode == Activity.RESULT_OK && data != null && data.hasExtra(ClearActivity.DB_CHANGE)) { 81 | fragment.setDBChange(data.getBooleanExtra(ClearActivity.DB_CHANGE, false)); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/Album.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | import io.github.mthli.Ninja.Browser.AlbumController; 10 | import io.github.mthli.Ninja.Browser.BrowserController; 11 | import io.github.mthli.Ninja.R; 12 | 13 | public class Album { 14 | private Context context; 15 | 16 | private View albumView; 17 | public View getAlbumView() { 18 | return albumView; 19 | } 20 | 21 | private ImageView albumCover; 22 | public void setAlbumCover(Bitmap bitmap) { 23 | albumCover.setImageBitmap(bitmap); 24 | } 25 | 26 | private TextView albumTitle; 27 | public String getAlbumTitle() { 28 | return albumTitle.getText().toString(); 29 | } 30 | public void setAlbumTitle(String title) { 31 | albumTitle.setText(title); 32 | } 33 | 34 | private AlbumController albumController; 35 | public void setAlbumController(AlbumController albumController) { 36 | this.albumController = albumController; 37 | } 38 | 39 | private BrowserController browserController; 40 | public void setBrowserController(BrowserController browserController) { 41 | this.browserController = browserController; 42 | } 43 | 44 | public Album(Context context, AlbumController albumController, BrowserController browserController) { 45 | this.context = context; 46 | this.albumController = albumController; 47 | this.browserController = browserController; 48 | initUI(); 49 | } 50 | 51 | private void initUI() { 52 | albumView = LayoutInflater.from(context).inflate(R.layout.album, null, false); 53 | 54 | albumView.setOnTouchListener(new SwipeToDismissListener( 55 | albumView, 56 | null, 57 | new SwipeToDismissListener.DismissCallback() { 58 | @Override 59 | public boolean canDismiss(Object token) { 60 | return true; 61 | } 62 | 63 | @Override 64 | public void onDismiss(View view, Object token) { 65 | browserController.removeAlbum(albumController); 66 | } 67 | } 68 | )); 69 | 70 | albumView.setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | browserController.showAlbum(albumController, false, false, false); 74 | } 75 | }); 76 | 77 | albumView.setOnLongClickListener(new View.OnLongClickListener() { 78 | @Override 79 | public boolean onLongClick(View v) { 80 | NinjaToast.show(context, albumTitle.getText().toString()); 81 | return true; 82 | } 83 | }); 84 | 85 | albumCover = (ImageView) albumView.findViewById(R.id.album_cover); 86 | albumTitle = (TextView) albumView.findViewById(R.id.album_title); 87 | albumTitle.setText(context.getString(R.string.album_untitled)); 88 | } 89 | 90 | public void activate() { 91 | albumView.setBackgroundResource(R.drawable.album_shape_blue); 92 | } 93 | 94 | public void deactivate() { 95 | albumView.setBackgroundResource(R.drawable.album_shape_dark); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/NinjaWebChromeClient.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.net.Uri; 4 | import android.os.Message; 5 | import android.view.View; 6 | import android.webkit.*; 7 | import io.github.mthli.Ninja.View.NinjaWebView; 8 | 9 | public class NinjaWebChromeClient extends WebChromeClient { 10 | private NinjaWebView ninjaWebView; 11 | 12 | public NinjaWebChromeClient(NinjaWebView ninjaWebView) { 13 | super(); 14 | this.ninjaWebView = ninjaWebView; 15 | } 16 | 17 | @Override 18 | public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 19 | ninjaWebView.getBrowserController().onCreateView(view, resultMsg); 20 | return isUserGesture; 21 | } 22 | 23 | @Override 24 | public void onCloseWindow(WebView view) { 25 | super.onCloseWindow(view); 26 | } 27 | 28 | @Override 29 | public void onProgressChanged(WebView view, int progress) { 30 | super.onProgressChanged(view, progress); 31 | ninjaWebView.update(progress); 32 | } 33 | 34 | @Override 35 | public void onReceivedTitle(WebView view, String title) { 36 | super.onReceivedTitle(view, title); 37 | ninjaWebView.update(title, view.getUrl()); 38 | } 39 | 40 | @Deprecated 41 | @Override 42 | public void onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback) { 43 | ninjaWebView.getBrowserController().onShowCustomView(view, requestedOrientation, callback); 44 | super.onShowCustomView(view, requestedOrientation, callback); 45 | } 46 | 47 | @Override 48 | public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { 49 | ninjaWebView.getBrowserController().onShowCustomView(view, callback); 50 | super.onShowCustomView(view, callback); 51 | } 52 | 53 | @Override 54 | public void onHideCustomView() { 55 | ninjaWebView.getBrowserController().onHideCustomView(); 56 | super.onHideCustomView(); 57 | } 58 | 59 | /* For 4.1 to 4.4 */ 60 | public void openFileChooser(ValueCallback uploadMsg) { 61 | ninjaWebView.getBrowserController().openFileChooser(uploadMsg); 62 | } 63 | 64 | /* For 4.1 to 4.4 */ 65 | public void openFileChooser(ValueCallback uploadMsg, String acceptType) { 66 | ninjaWebView.getBrowserController().openFileChooser(uploadMsg); 67 | } 68 | 69 | /* For 4.1 to 4.4 */ 70 | public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { 71 | ninjaWebView.getBrowserController().openFileChooser(uploadMsg); 72 | } 73 | 74 | @Override 75 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { 76 | ninjaWebView.getBrowserController().showFileChooser(filePathCallback, fileChooserParams); 77 | return true; 78 | } 79 | 80 | /** 81 | * TODO: ?support this method 82 | * @link http://developer.android.com/reference/android/webkit/WebChromeClient.html#onGeolocationPermissionsShowPrompt%28java.lang.String,%20android.webkit.GeolocationPermissions.Callback%29 83 | * @param origin 84 | * @param callback 85 | */ 86 | @Override 87 | public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { 88 | super.onGeolocationPermissionsShowPrompt(origin, callback); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Service/HolderService.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Service; 2 | 3 | import android.app.Notification; 4 | import android.app.Service; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.IBinder; 8 | import android.os.Message; 9 | import android.view.View; 10 | import android.webkit.ValueCallback; 11 | import android.webkit.WebChromeClient; 12 | import android.webkit.WebView; 13 | import io.github.mthli.Ninja.Browser.AlbumController; 14 | import io.github.mthli.Ninja.Browser.BrowserContainer; 15 | import io.github.mthli.Ninja.Browser.BrowserController; 16 | import io.github.mthli.Ninja.R; 17 | import io.github.mthli.Ninja.Unit.*; 18 | import io.github.mthli.Ninja.View.NinjaContextWrapper; 19 | import io.github.mthli.Ninja.View.NinjaWebView; 20 | 21 | public class HolderService extends Service implements BrowserController { 22 | @Override 23 | public void updateAutoComplete() {} 24 | 25 | @Override 26 | public void updateBookmarks() {} 27 | 28 | @Override 29 | public void updateInputBox(String query) {} 30 | 31 | @Override 32 | public void updateProgress(int progress) {} 33 | 34 | @Override 35 | public void showAlbum(AlbumController albumController, boolean anim, boolean expand, boolean capture) {} 36 | 37 | @Override 38 | public void removeAlbum(AlbumController albumController) {} 39 | 40 | @Override 41 | public void openFileChooser(ValueCallback uploadMsg) {} 42 | 43 | @Override 44 | public void showFileChooser(ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {} 45 | 46 | @Override 47 | public void onCreateView(WebView view, Message resultMsg) {} 48 | 49 | @Override 50 | public boolean onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback) { 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { 56 | return true; 57 | } 58 | 59 | @Override 60 | public boolean onHideCustomView() { 61 | return true; 62 | } 63 | 64 | @Override 65 | public void onLongPress(String url) {} 66 | 67 | @Override 68 | public int onStartCommand(Intent intent, int flags, int startId) { 69 | NinjaWebView webView = new NinjaWebView(new NinjaContextWrapper(this)); 70 | webView.setBrowserController(this); 71 | webView.setFlag(BrowserUnit.FLAG_NINJA); 72 | webView.setAlbumCover(null); 73 | webView.setAlbumTitle(getString(R.string.album_untitled)); 74 | ViewUnit.bound(this, webView); 75 | 76 | webView.loadUrl(RecordUnit.getHolder().getURL()); 77 | webView.deactivate(); 78 | 79 | BrowserContainer.add(webView); 80 | updateNotification(); 81 | 82 | return START_STICKY; 83 | } 84 | 85 | @Override 86 | public void onDestroy() { 87 | if (IntentUnit.isClear()) { 88 | BrowserContainer.clear(); 89 | } 90 | stopForeground(true); 91 | super.onDestroy(); 92 | } 93 | 94 | @Override 95 | public IBinder onBind(Intent intent) { 96 | return null; 97 | } 98 | 99 | private void updateNotification() { 100 | Notification notification = NotificationUnit.getHBuilder(this).build(); 101 | startForeground(NotificationUnit.HOLDER_ID, notification); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Ninja/assets/ninja_introduction_en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | Ninja introduction 22 | 23 |
24 | 25 |

Ninja introduction

26 | 27 |

The author has to prepare for the final exam, so MAYBE pause updates.

28 | 29 |

Basically Ninja is a simple web browser like any other, but there are some different things you need to know:

30 | 31 |

Custom home:

32 | 33 |
  • On the applications first start, home page shows as about:blank.

  • Use the overflow menu's Add to home to pin webpages to Homepage.

  • Use the overflow menu's Relayout to customize your homepage layout.

  • Long press on a card on the homepage, and you can edit the title.

34 | 35 |

Switch tabs:

36 | 37 |
  • You can set tab switcher position to be at the screen top or screen bottom at Settings/Browser/Omnibox Position.

  • Press the address bar and drag it down or up, then the fashion tab switcher will display.

  • Swipe up/down to dimiss a page.

  • Or just swipe the omnibox left/right to swicth tabs :)

38 | 39 |

The Settings is located in the top or bottom left of tab switcher, the gear icon.

40 | 41 |

Remember that if the soft keyboard is shown the tab switcher would not display, it's our design :)

42 | 43 |

Load in background when you click links in other App:

44 | 45 |
  1. Set Ninja as your default browser when the link is clicked.

  2. Single tap will open the links in the background, and show a clickable notification in the statusbar.

  3. Double tap will show a dialog that allows you to open links in foreground, etc.

46 | 47 |

Volume control:

48 | 49 |

In Settings/Browser/Volume control you can set the volume keys behavior to:

50 | 51 |
  • Switch tabs.

  • Scroll webpages.

  • System default.

52 | 53 |

AdBlock whitelist:

54 | 55 |

Since AdBlock maybe cause some websites display error, you can add those websites to the whitelist Settings/AdBlock/Whitelist.

56 | 57 |

Readability:

58 | 59 |

Ninja supports read mode but it needs Readability API Token.

60 | 61 |

To get the token you should go to Readability Developer APIs and set it in Settings/Readability/Token.

62 | 63 |

Screenshot:

64 | 65 |

Ninja can capture entire webpages!

66 | 67 |

But that does not mean you could screenshot a very long webpage which may cause the browser to go Out of Memory.

68 | 69 |
70 | 71 |

2015.06.19 Matthew Lee

72 | 73 |
-------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Activity/ClearActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Activity; 2 | 3 | import android.app.Activity; 4 | import android.app.ProgressDialog; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.os.Bundle; 8 | import android.preference.PreferenceManager; 9 | import android.view.KeyEvent; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import io.github.mthli.Ninja.Fragment.ClearFragment; 13 | import io.github.mthli.Ninja.R; 14 | import io.github.mthli.Ninja.Unit.BrowserUnit; 15 | import io.github.mthli.Ninja.View.NinjaToast; 16 | 17 | public class ClearActivity extends Activity { 18 | public static final String DB_CHANGE = "DB_CHANGE"; 19 | private boolean dbChange = false; 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | getActionBar().setDisplayHomeAsUpEnabled(true); 25 | 26 | getFragmentManager().beginTransaction().replace(android.R.id.content, new ClearFragment()).commit(); 27 | } 28 | 29 | @Override 30 | public boolean onCreateOptionsMenu(Menu menu) { 31 | getMenuInflater().inflate(R.menu.clear_menu, menu); 32 | return super.onCreateOptionsMenu(menu); 33 | } 34 | 35 | @Override 36 | public boolean onOptionsItemSelected(MenuItem menuItem) { 37 | switch (menuItem.getItemId()) { 38 | case android.R.id.home: 39 | Intent intent = new Intent(); 40 | intent.putExtra(DB_CHANGE, dbChange); 41 | setResult(Activity.RESULT_OK, intent); 42 | finish(); 43 | break; 44 | case R.id.clear_menu_done_all: 45 | clear(); 46 | break; 47 | default: 48 | break; 49 | } 50 | return true; 51 | } 52 | 53 | @Override 54 | public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { 55 | Intent intent = new Intent(); 56 | intent.putExtra(DB_CHANGE, dbChange); 57 | setResult(Activity.RESULT_OK, intent); 58 | finish(); 59 | return true; 60 | } 61 | 62 | private void clear() { 63 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 64 | boolean clearBookmarks = sp.getBoolean(getString(R.string.sp_clear_bookmarks), false); 65 | boolean clearCache = sp.getBoolean(getString(R.string.sp_clear_cache), true); 66 | boolean clearCookie = sp.getBoolean(getString(R.string.sp_clear_cookie), false); 67 | boolean clearFormData = sp.getBoolean(getString(R.string.sp_clear_form_data), false); 68 | boolean clearHistory = sp.getBoolean(getString(R.string.sp_clear_history), true); 69 | boolean clearPasswords = sp.getBoolean(getString(R.string.sp_clear_passwords), false); 70 | 71 | ProgressDialog dialog; 72 | dialog = new ProgressDialog(this); 73 | dialog.setCancelable(false); 74 | dialog.setMessage(getString(R.string.toast_wait_a_minute)); 75 | dialog.show(); 76 | 77 | if (clearBookmarks) { 78 | BrowserUnit.clearBookmarks(this); 79 | } 80 | if (clearCache) { 81 | BrowserUnit.clearCache(this); 82 | } 83 | if (clearCookie) { 84 | BrowserUnit.clearCookie(this); 85 | } 86 | if (clearFormData) { 87 | BrowserUnit.clearFormData(this); 88 | } 89 | if (clearHistory) { 90 | BrowserUnit.clearHistory(this); 91 | } 92 | if (clearPasswords) { 93 | BrowserUnit.clearPasswords(this); 94 | } 95 | 96 | dialog.hide(); 97 | dialog.dismiss(); 98 | 99 | dbChange = true; 100 | NinjaToast.show(this, R.string.toast_clear_successful); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Ninja/res/values-zh-rCN/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 前台打开 8 | 拷贝链接 9 | 分享 10 | 11 | 12 | 13 | 14 | 新建标签 15 | 拷贝链接 16 | 分享 17 | 编辑 18 | 删除 19 | 20 | 21 | 22 | 23 | 回到顶部 24 | 添加拨号 25 | 页内搜索 26 | 网页截图 27 | 阅读模式 28 | 分享 29 | 排版 30 | 退出 31 | 32 | 33 | 34 | 35 | @string/setting_summary_search_engine_google 36 | @string/setting_summary_search_engine_duckduckgo 37 | @string/setting_summary_search_engine_startpage 38 | @string/setting_summary_search_engine_bing 39 | @string/setting_summary_search_engine_baidu 40 | 41 | 42 | 0 43 | 1 44 | 2 45 | 3 46 | 4 47 | 48 | 49 | 50 | 51 | @string/setting_summary_notification_priority_default 52 | @string/setting_summary_notification_priority_high 53 | @string/setting_summary_notification_priority_low 54 | 55 | 56 | 0 57 | 1 58 | 2 59 | 60 | 61 | 62 | 63 | @string/setting_summary_omnibox_position_top 64 | @string/setting_summary_omnibox_position_bottom 65 | 66 | 67 | 0 68 | 1 69 | 70 | 71 | 72 | 73 | @string/setting_summary_vc_switch_tabs 74 | @string/setting_summary_vc_scroll_webpage 75 | @string/setting_summary_vc_system_default 76 | 77 | 78 | 0 79 | 1 80 | 2 81 | 82 | 83 | 84 | 85 | @string/setting_summary_user_agent_default 86 | @string/setting_summary_user_agent_desktop 87 | 88 | 89 | 0 90 | 1 91 | 92 | 93 | 94 | 95 | @string/setting_summary_rendering_default 96 | @string/setting_summary_rendering_grayscale 97 | @string/setting_summary_rendering_inverted 98 | @string/setting_summary_rendering_inverted_grayscale 99 | 100 | 101 | 0 102 | 1 103 | 2 104 | 3 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Ninja/res/values-zh-rTW/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 前台打開 8 | 拷貝鏈接 9 | 分享 10 | 11 | 12 | 13 | 14 | 新建標簽 15 | 拷貝鏈接 16 | 分享 17 | 編輯 18 | 刪除 19 | 20 | 21 | 22 | 23 | 回到頂部 24 | 添加撥號 25 | 頁內搜索 26 | 網頁截圖 27 | 閱讀模式 28 | 分享 29 | 排版 30 | 退出 31 | 32 | 33 | 34 | 35 | @string/setting_summary_search_engine_google 36 | @string/setting_summary_search_engine_duckduckgo 37 | @string/setting_summary_search_engine_startpage 38 | @string/setting_summary_search_engine_bing 39 | @string/setting_summary_search_engine_baidu 40 | 41 | 42 | 0 43 | 1 44 | 2 45 | 3 46 | 4 47 | 48 | 49 | 50 | 51 | @string/setting_summary_notification_priority_default 52 | @string/setting_summary_notification_priority_high 53 | @string/setting_summary_notification_priority_low 54 | 55 | 56 | 0 57 | 1 58 | 2 59 | 60 | 61 | 62 | 63 | @string/setting_summary_omnibox_position_top 64 | @string/setting_summary_omnibox_position_bottom 65 | 66 | 67 | 0 68 | 1 69 | 70 | 71 | 72 | 73 | @string/setting_summary_vc_switch_tabs 74 | @string/setting_summary_vc_scroll_webpage 75 | @string/setting_summary_vc_system_default 76 | 77 | 78 | 0 79 | 1 80 | 2 81 | 82 | 83 | 84 | 85 | @string/setting_summary_user_agent_default 86 | @string/setting_summary_user_agent_desktop 87 | 88 | 89 | 0 90 | 1 91 | 92 | 93 | 94 | 95 | @string/setting_summary_rendering_default 96 | @string/setting_summary_rendering_grayscale 97 | @string/setting_summary_rendering_inverted 98 | @string/setting_summary_rendering_inverted_grayscale 99 | 100 | 101 | 0 102 | 1 103 | 2 104 | 3 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Browser/AdBlock.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Browser; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import io.github.mthli.Ninja.Database.RecordAction; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.util.*; 13 | 14 | public class AdBlock { 15 | private static final String FILE = "hosts.txt"; 16 | private static final Set hosts = new HashSet<>(); 17 | private static final List whitelist = new ArrayList<>(); 18 | private static final Locale locale = Locale.getDefault(); 19 | 20 | private static void loadHosts(final Context context) { 21 | Thread thread = new Thread(new Runnable() { 22 | @Override 23 | public void run() { 24 | AssetManager manager = context.getAssets(); 25 | try { 26 | BufferedReader reader = new BufferedReader(new InputStreamReader(manager.open(FILE))); 27 | String line; 28 | while ((line = reader.readLine()) != null) { 29 | hosts.add(line.toLowerCase(locale)); 30 | } 31 | } catch (IOException i) {} 32 | } 33 | }); 34 | thread.start(); 35 | } 36 | 37 | private synchronized static void loadDomains(Context context) { 38 | RecordAction action = new RecordAction(context); 39 | action.open(false); 40 | whitelist.clear(); 41 | for (String domain : action.listDomains()) { 42 | whitelist.add(domain); 43 | } 44 | action.close(); 45 | } 46 | 47 | private static String getDomain(String url) throws URISyntaxException { 48 | url = url.toLowerCase(locale); 49 | 50 | int index = url.indexOf('/', 8); // -> http://(7) and https://(8) 51 | if (index != -1) { 52 | url = url.substring(0, index); 53 | } 54 | 55 | URI uri = new URI(url); 56 | String domain = uri.getHost(); 57 | if (domain == null) { 58 | return url; 59 | } 60 | return domain.startsWith("www.") ? domain.substring(4) : domain; 61 | } 62 | 63 | private Context context; 64 | 65 | public AdBlock(Context context) { 66 | this.context = context; 67 | 68 | if (hosts.isEmpty()) { 69 | loadHosts(context); 70 | } 71 | loadDomains(context); 72 | } 73 | 74 | public boolean isWhite(String url) { 75 | for (String domain : whitelist) { 76 | if (url.contains(domain)) { 77 | return true; 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | public boolean isAd(String url) { 84 | String domain; 85 | try { 86 | domain = getDomain(url); 87 | } catch (URISyntaxException u) { 88 | return false; 89 | } 90 | return hosts.contains(domain.toLowerCase(locale)); 91 | } 92 | 93 | public synchronized void addDomain(String domain) { 94 | RecordAction action = new RecordAction(context); 95 | action.open(true); 96 | action.addDomain(domain); 97 | action.close(); 98 | whitelist.add(domain); 99 | } 100 | 101 | public synchronized void removeDomain(String domain) { 102 | RecordAction action = new RecordAction(context); 103 | action.open(true); 104 | action.deleteDomain(domain); 105 | action.close(); 106 | whitelist.remove(domain); 107 | } 108 | 109 | public synchronized void clearDomains() { 110 | RecordAction action = new RecordAction(context); 111 | action.open(true); 112 | action.clearDomains(); 113 | action.close(); 114 | whitelist.clear(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Ninja/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Foreground 8 | Copy link 9 | Share 10 | 11 | 12 | 13 | 14 | New tab 15 | Copy link 16 | Share 17 | Edit 18 | Delete 19 | 20 | 21 | 22 | 23 | Go to top 24 | Add to home 25 | Find in page 26 | Screenshot 27 | Readability 28 | Share 29 | Relayout 30 | Quit 31 | 32 | 33 | 34 | 35 | @string/setting_summary_search_engine_google 36 | @string/setting_summary_search_engine_duckduckgo 37 | @string/setting_summary_search_engine_startpage 38 | @string/setting_summary_search_engine_bing 39 | @string/setting_summary_search_engine_baidu 40 | 41 | 42 | 0 43 | 1 44 | 2 45 | 3 46 | 4 47 | 48 | 49 | 50 | 51 | @string/setting_summary_notification_priority_default 52 | @string/setting_summary_notification_priority_high 53 | @string/setting_summary_notification_priority_low 54 | 55 | 56 | 0 57 | 1 58 | 2 59 | 60 | 61 | 62 | 63 | @string/setting_summary_omnibox_position_top 64 | @string/setting_summary_omnibox_position_bottom 65 | 66 | 67 | 0 68 | 1 69 | 70 | 71 | 72 | 73 | @string/setting_summary_vc_switch_tabs 74 | @string/setting_summary_vc_scroll_webpage 75 | @string/setting_summary_vc_system_default 76 | 77 | 78 | 0 79 | 1 80 | 2 81 | 82 | 83 | 84 | 85 | @string/setting_summary_user_agent_default 86 | @string/setting_summary_user_agent_desktop 87 | 88 | 89 | 0 90 | 1 91 | 92 | 93 | 94 | 95 | @string/setting_summary_rendering_default 96 | @string/setting_summary_rendering_grayscale 97 | @string/setting_summary_rendering_inverted 98 | @string/setting_summary_rendering_inverted_grayscale 99 | 100 | 101 | 0 102 | 1 103 | 2 104 | 3 105 | 106 | 107 | -------------------------------------------------------------------------------- /Ninja/res/values-fr/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Premier plan 8 | Copier le lien 9 | Partager 10 | 11 | 12 | 13 | 14 | Nouvel onglet 15 | Copier le lien 16 | Partager 17 | Modifier 18 | Supprimer 19 | 20 | 21 | 22 | 23 | Haut de page 24 | Ajouter à l\'accueil 25 | Trouver dans la page 26 | Capture d\'écran 27 | Lisibilité 28 | Partager 29 | Réarranger 30 | Quitter 31 | 32 | 33 | 34 | 35 | @string/setting_summary_search_engine_google 36 | @string/setting_summary_search_engine_duckduckgo 37 | @string/setting_summary_search_engine_startpage 38 | @string/setting_summary_search_engine_bing 39 | @string/setting_summary_search_engine_baidu 40 | 41 | 42 | 0 43 | 1 44 | 2 45 | 3 46 | 4 47 | 48 | 49 | 50 | 51 | @string/setting_summary_notification_priority_default 52 | @string/setting_summary_notification_priority_high 53 | @string/setting_summary_notification_priority_low 54 | 55 | 56 | 0 57 | 1 58 | 2 59 | 60 | 61 | 62 | 63 | @string/setting_summary_omnibox_position_top 64 | @string/setting_summary_omnibox_position_bottom 65 | 66 | 67 | 0 68 | 1 69 | 70 | 71 | 72 | 73 | @string/setting_summary_vc_switch_tabs 74 | @string/setting_summary_vc_scroll_webpage 75 | @string/setting_summary_vc_system_default 76 | 77 | 78 | 0 79 | 1 80 | 2 81 | 82 | 83 | 84 | 85 | @string/setting_summary_user_agent_default 86 | @string/setting_summary_user_agent_desktop 87 | 88 | 89 | 0 90 | 1 91 | 92 | 93 | 94 | 95 | @string/setting_summary_rendering_default 96 | @string/setting_summary_rendering_grayscale 97 | @string/setting_summary_rendering_inverted 98 | @string/setting_summary_rendering_inverted_grayscale 99 | 100 | 101 | 0 102 | 1 103 | 2 104 | 3 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Unit/ViewUnit.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Unit; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.*; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Build; 8 | import android.util.DisplayMetrics; 9 | import android.util.TypedValue; 10 | import android.view.View; 11 | import io.github.mthli.Ninja.R; 12 | 13 | public class ViewUnit { 14 | public static void bound(Context context, View view) { 15 | int windowWidth = getWindowWidth(context); 16 | int windowHeight = getWindowHeight(context); 17 | int statusBarHeight = getStatusBarHeight(context); 18 | int dimen48dp = context.getResources().getDimensionPixelOffset(R.dimen.layout_height_48dp); 19 | 20 | int widthSpec = View.MeasureSpec.makeMeasureSpec(windowWidth, View.MeasureSpec.EXACTLY); 21 | int heightSpec = View.MeasureSpec.makeMeasureSpec(windowHeight - statusBarHeight - dimen48dp, View.MeasureSpec.EXACTLY); 22 | 23 | view.measure(widthSpec, heightSpec); 24 | view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); 25 | } 26 | 27 | public static Bitmap capture(View view, float width, float height, boolean scroll, Bitmap.Config config) { 28 | if (!view.isDrawingCacheEnabled()) { 29 | view.setDrawingCacheEnabled(true); 30 | } 31 | 32 | Bitmap bitmap = Bitmap.createBitmap((int) width, (int) height, config); 33 | bitmap.eraseColor(Color.WHITE); 34 | 35 | Canvas canvas = new Canvas(bitmap); 36 | int left = view.getLeft(); 37 | int top = view.getTop(); 38 | if (scroll) { 39 | left = view.getScrollX(); 40 | top = view.getScrollY(); 41 | } 42 | int status = canvas.save(); 43 | canvas.translate(-left, -top); 44 | 45 | float scale = width / view.getWidth(); 46 | canvas.scale(scale, scale, left, top); 47 | 48 | view.draw(canvas); 49 | canvas.restoreToCount(status); 50 | 51 | Paint alphaPaint = new Paint(); 52 | alphaPaint.setColor(Color.TRANSPARENT); 53 | 54 | canvas.drawRect(0f, 0f, 1f, height, alphaPaint); 55 | canvas.drawRect(width - 1f, 0f, width, height, alphaPaint); 56 | canvas.drawRect(0f, 0f, width, 1f, alphaPaint); 57 | canvas.drawRect(0f, height - 1f, width, height, alphaPaint); 58 | canvas.setBitmap(null); 59 | 60 | return bitmap; 61 | } 62 | 63 | public static float dp2px(Context context, float dp) { 64 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 65 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); 66 | } 67 | 68 | public static float getDensity(Context context) { 69 | return context.getResources().getDisplayMetrics().density; 70 | } 71 | 72 | public static Drawable getDrawable(Context context, int id) { 73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 74 | return context.getResources().getDrawable(id, null); 75 | } else { 76 | return context.getResources().getDrawable(id); 77 | } 78 | } 79 | 80 | public static int getStatusBarHeight(Context context) { 81 | Resources resources = context.getResources(); 82 | int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); 83 | if (resourceId > 0) { 84 | return resources.getDimensionPixelSize(resourceId); 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | public static int getWindowHeight(Context context) { 91 | return context.getResources().getDisplayMetrics().heightPixels; 92 | } 93 | 94 | public static int getWindowWidth(Context context) { 95 | return context.getResources().getDisplayMetrics().widthPixels; 96 | } 97 | 98 | public static void setElevation(View view, float elevation) { 99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 100 | view.setElevation(elevation); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /Ninja/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Ninja/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | 18 | 27 | 28 | 37 | 38 | 47 | 48 | 57 | 58 | 67 | 68 | 72 | 73 | 77 | 78 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/UserAgentListPreference.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.SharedPreferences; 7 | import android.preference.ListPreference; 8 | import android.preference.PreferenceManager; 9 | import android.text.InputType; 10 | import android.util.AttributeSet; 11 | import android.view.KeyEvent; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.inputmethod.EditorInfo; 15 | import android.view.inputmethod.InputMethodManager; 16 | import android.widget.EditText; 17 | import android.widget.FrameLayout; 18 | import android.widget.TextView; 19 | import io.github.mthli.Ninja.R; 20 | 21 | public class UserAgentListPreference extends ListPreference { 22 | public UserAgentListPreference(Context context) { 23 | super(context); 24 | } 25 | 26 | public UserAgentListPreference(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | @SuppressWarnings("New API") 31 | public UserAgentListPreference(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | } 34 | 35 | @SuppressWarnings("New API") 36 | public UserAgentListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 37 | super(context, attrs, defStyleAttr, defStyleRes); 38 | } 39 | 40 | @Override 41 | protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { 42 | super.onPrepareDialogBuilder(builder); 43 | 44 | builder.setNeutralButton(R.string.dialog_button_custom, new DialogInterface.OnClickListener() { 45 | @Override 46 | public void onClick(DialogInterface dialog, int which) { 47 | showEditDialog(); 48 | } 49 | }); 50 | } 51 | 52 | private void showEditDialog() { 53 | final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); 54 | AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 55 | builder.setCancelable(true); 56 | 57 | FrameLayout layout = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.dialog_edit, null, false); 58 | builder.setView(layout); 59 | 60 | final AlertDialog dialog = builder.create(); 61 | dialog.show(); 62 | 63 | final EditText editText = (EditText) layout.findViewById(R.id.dialog_edit); 64 | editText.setHint(R.string.dialog_ua_hint); 65 | String custom = sp.getString(getContext().getString(R.string.sp_user_agent_custom), ""); 66 | editText.setText(custom); 67 | editText.setSelection(custom.length()); 68 | editText.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); 69 | showSoftInput(editText); 70 | 71 | editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { 72 | @Override 73 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 74 | if (actionId != EditorInfo.IME_ACTION_DONE) { 75 | return false; 76 | } 77 | 78 | String ua = editText.getText().toString().trim(); 79 | if (ua.isEmpty()) { 80 | NinjaToast.show(getContext(), R.string.toast_input_empty); 81 | return true; 82 | } else { 83 | sp.edit().putString(getContext().getString(R.string.sp_user_agent), "2").commit(); 84 | sp.edit().putString(getContext().getString(R.string.sp_user_agent_custom), ua).commit(); 85 | 86 | hideSoftInput(editText); 87 | dialog.hide(); 88 | dialog.dismiss(); 89 | return false; 90 | } 91 | } 92 | }); 93 | } 94 | 95 | private void hideSoftInput(View view) { 96 | view.clearFocus(); 97 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 98 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 99 | } 100 | 101 | private void showSoftInput(View view) { 102 | view.requestFocus(); 103 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 104 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Ninja/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 92 | 93 | 94 | 95 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/SearchEngineListPreference.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.SharedPreferences; 7 | import android.preference.ListPreference; 8 | import android.preference.PreferenceManager; 9 | import android.util.AttributeSet; 10 | import android.view.KeyEvent; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.inputmethod.EditorInfo; 14 | import android.view.inputmethod.InputMethodManager; 15 | import android.widget.EditText; 16 | import android.widget.FrameLayout; 17 | import android.widget.TextView; 18 | import io.github.mthli.Ninja.R; 19 | import io.github.mthli.Ninja.Unit.BrowserUnit; 20 | 21 | public class SearchEngineListPreference extends ListPreference { 22 | public SearchEngineListPreference(Context context) { 23 | super(context); 24 | } 25 | 26 | public SearchEngineListPreference(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | @SuppressWarnings("New API") 31 | public SearchEngineListPreference(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | } 34 | 35 | @SuppressWarnings("New API") 36 | public SearchEngineListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 37 | super(context, attrs, defStyleAttr, defStyleRes); 38 | } 39 | 40 | @Override 41 | protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { 42 | super.onPrepareDialogBuilder(builder); 43 | 44 | builder.setNeutralButton(R.string.dialog_button_custom, new DialogInterface.OnClickListener() { 45 | @Override 46 | public void onClick(DialogInterface dialog, int which) { 47 | showEditDialog(); 48 | } 49 | }); 50 | } 51 | 52 | private void showEditDialog() { 53 | final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); 54 | AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 55 | builder.setCancelable(true); 56 | 57 | FrameLayout layout = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.dialog_edit, null, false); 58 | builder.setView(layout); 59 | 60 | final AlertDialog dialog = builder.create(); 61 | dialog.show(); 62 | 63 | final EditText editText = (EditText) layout.findViewById(R.id.dialog_edit); 64 | editText.setHint(R.string.dialog_se_hint); 65 | String custom = sp.getString(getContext().getString(R.string.sp_search_engine_custom), ""); 66 | editText.setText(custom); 67 | editText.setSelection(custom.length()); 68 | showSoftInput(editText); 69 | 70 | editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { 71 | @Override 72 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 73 | if (actionId != EditorInfo.IME_ACTION_DONE) { 74 | return false; 75 | } 76 | 77 | String domain = editText.getText().toString().trim(); 78 | if (domain.isEmpty()) { 79 | NinjaToast.show(getContext(), R.string.toast_input_empty); 80 | return true; 81 | } else if (!BrowserUnit.isURL(domain)) { 82 | NinjaToast.show(getContext(), R.string.toast_invalid_domain); 83 | return true; 84 | } else { 85 | sp.edit().putString(getContext().getString(R.string.sp_search_engine), "5").commit(); 86 | sp.edit().putString(getContext().getString(R.string.sp_search_engine_custom), domain).commit(); 87 | 88 | hideSoftInput(editText); 89 | dialog.hide(); 90 | dialog.dismiss(); 91 | return false; 92 | } 93 | } 94 | }); 95 | } 96 | 97 | private void hideSoftInput(View view) { 98 | view.clearFocus(); 99 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 100 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 101 | } 102 | 103 | private void showSoftInput(View view) { 104 | view.requestFocus(); 105 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 106 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/Activity/WhitelistActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.Activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.view.inputmethod.InputMethodManager; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.ListView; 13 | import io.github.mthli.Ninja.Browser.AdBlock; 14 | import io.github.mthli.Ninja.Database.RecordAction; 15 | import io.github.mthli.Ninja.R; 16 | import io.github.mthli.Ninja.Unit.BrowserUnit; 17 | import io.github.mthli.Ninja.View.NinjaToast; 18 | import io.github.mthli.Ninja.View.WhitelistAdapter; 19 | 20 | import java.util.List; 21 | 22 | public class WhitelistActivity extends Activity { 23 | private WhitelistAdapter adapter; 24 | private List list; 25 | 26 | @Override 27 | public void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.whitelist); 30 | getActionBar().setDisplayHomeAsUpEnabled(true); 31 | 32 | RecordAction action = new RecordAction(this); 33 | action.open(false); 34 | list = action.listDomains(); 35 | action.close(); 36 | 37 | ListView listView = (ListView) findViewById(R.id.whitelist); 38 | listView.setEmptyView(findViewById(R.id.whitelist_empty)); 39 | 40 | adapter = new WhitelistAdapter(this, R.layout.whitelist_item, list); 41 | listView.setAdapter(adapter); 42 | adapter.notifyDataSetChanged(); 43 | 44 | final EditText editText = (EditText) findViewById(R.id.whilelist_edit); 45 | showSoftInput(editText); 46 | 47 | Button button = (Button) findViewById(R.id.whilelist_add); 48 | button.setOnClickListener(new View.OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | String domain = editText.getText().toString().trim(); 52 | if (domain.isEmpty()) { 53 | NinjaToast.show(WhitelistActivity.this, R.string.toast_input_empty); 54 | } else if (!BrowserUnit.isURL(domain)) { 55 | NinjaToast.show(WhitelistActivity.this, R.string.toast_invalid_domain); 56 | } else { 57 | RecordAction action = new RecordAction(WhitelistActivity.this); 58 | action.open(true); 59 | if (action.checkDomain(domain)) { 60 | NinjaToast.show(WhitelistActivity.this, R.string.toast_domain_already_exists); 61 | } else { 62 | AdBlock adBlock = new AdBlock(WhitelistActivity.this); 63 | adBlock.addDomain(domain.trim()); 64 | list.add(0, domain.trim()); 65 | adapter.notifyDataSetChanged(); 66 | NinjaToast.show(WhitelistActivity.this, R.string.toast_add_whitelist_successful); 67 | } 68 | action.close(); 69 | } 70 | } 71 | }); 72 | } 73 | 74 | @Override 75 | public void onPause() { 76 | hideSoftInput(findViewById(R.id.whilelist_edit)); 77 | super.onPause(); 78 | } 79 | 80 | @Override 81 | public boolean onCreateOptionsMenu(Menu menu) { 82 | getMenuInflater().inflate(R.menu.whilelist_menu, menu); 83 | return super.onCreateOptionsMenu(menu); 84 | } 85 | 86 | @Override 87 | public boolean onOptionsItemSelected(MenuItem menuItem) { 88 | switch (menuItem.getItemId()) { 89 | case android.R.id.home: 90 | finish(); 91 | break; 92 | case R.id.whitelist_menu_clear: 93 | AdBlock adBlock = new AdBlock(this); 94 | adBlock.clearDomains(); 95 | list.clear(); 96 | adapter.notifyDataSetChanged(); 97 | break; 98 | default: 99 | break; 100 | } 101 | return true; 102 | } 103 | 104 | private void hideSoftInput(View view) { 105 | view.clearFocus(); 106 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 107 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 108 | } 109 | 110 | private void showSoftInput(View view) { 111 | view.requestFocus(); 112 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 113 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Ninja/src/io/github/mthli/Ninja/View/SwipeToBoundListener.java: -------------------------------------------------------------------------------- 1 | package io.github.mthli.Ninja.View; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.view.*; 6 | import io.github.mthli.Ninja.Unit.ViewUnit; 7 | 8 | public class SwipeToBoundListener implements View.OnTouchListener { 9 | public interface BoundCallback { 10 | boolean canSwipe(); 11 | void onSwipe(); 12 | void onBound(boolean canSwitch, boolean left); 13 | } 14 | 15 | private View view; 16 | private BoundCallback callback; 17 | 18 | private int targetWidth = 1; 19 | private int slop; 20 | private long animTime; 21 | 22 | private float downX; 23 | private float translationX; 24 | private boolean swiping; 25 | private boolean swipingLeft; 26 | private boolean canSwitch; 27 | private int swipingSlop; 28 | private VelocityTracker velocityTracker; 29 | 30 | public SwipeToBoundListener(View view, BoundCallback callback) { 31 | this.view = view; 32 | this.callback = callback; 33 | 34 | ViewConfiguration configuration = ViewConfiguration.get(this.view.getContext()); 35 | this.slop = configuration.getScaledTouchSlop(); 36 | this.animTime = this.view.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime); 37 | this.swiping = false; 38 | this.swipingLeft = false; 39 | this.canSwitch = false; 40 | } 41 | 42 | @Override 43 | public boolean onTouch(View v, MotionEvent event) { 44 | if (!callback.canSwipe()) { 45 | return false; 46 | } 47 | 48 | event.offsetLocation(translationX, 0); 49 | if (targetWidth < 2) { 50 | targetWidth = view.getWidth(); 51 | } 52 | 53 | switch (event.getActionMasked()) { 54 | case MotionEvent.ACTION_DOWN: { 55 | downX = event.getRawX(); 56 | 57 | velocityTracker = VelocityTracker.obtain(); 58 | velocityTracker.addMovement(event); 59 | 60 | return false; 61 | } case MotionEvent.ACTION_UP: { 62 | if (velocityTracker == null) { 63 | break; 64 | } 65 | 66 | velocityTracker.addMovement(event); 67 | velocityTracker.computeCurrentVelocity(1000); 68 | 69 | if (swiping) { 70 | view.animate() 71 | .translationX(0f) 72 | .setDuration(animTime) 73 | .setListener(new AnimatorListenerAdapter() { 74 | @Override 75 | public void onAnimationEnd(Animator animation) { 76 | callback.onBound(canSwitch, swipingLeft); 77 | } 78 | }); 79 | } 80 | 81 | downX = 0; 82 | translationX = 0; 83 | swiping = false; 84 | velocityTracker.recycle(); 85 | velocityTracker = null; 86 | 87 | break; 88 | } case MotionEvent.ACTION_CANCEL: { 89 | if (velocityTracker == null) { 90 | break; 91 | } 92 | 93 | view.animate() 94 | .translationX(0f) 95 | .setDuration(animTime) 96 | .setListener(null); 97 | 98 | downX = 0; 99 | translationX = 0; 100 | swiping = false; 101 | velocityTracker.recycle(); 102 | velocityTracker = null; 103 | 104 | break; 105 | } case MotionEvent.ACTION_MOVE: { 106 | if (velocityTracker == null) { 107 | break; 108 | } 109 | 110 | velocityTracker.addMovement(event); 111 | 112 | float deltaX = event.getRawX() - downX; 113 | if (Math.abs(deltaX) > slop) { 114 | swiping = true; 115 | swipingLeft = deltaX < 0; 116 | canSwitch = Math.abs(deltaX) >= ViewUnit.dp2px(view.getContext(), 48); // Can switch tabs when deltaX >= 48 to prevent misuse 117 | swipingSlop = (deltaX > 0 ? slop : -slop); 118 | view.getParent().requestDisallowInterceptTouchEvent(true); 119 | 120 | MotionEvent cancelEvent = MotionEvent.obtainNoHistory(event); 121 | cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); 122 | view.onTouchEvent(cancelEvent); 123 | cancelEvent.recycle(); 124 | } 125 | 126 | if (swiping) { 127 | translationX = deltaX; 128 | view.setTranslationX(deltaX - swipingSlop); 129 | callback.onSwipe(); 130 | 131 | return true; 132 | } 133 | 134 | break; 135 | } 136 | } 137 | 138 | return false; 139 | } 140 | } 141 | --------------------------------------------------------------------------------