├── app ├── .gitignore ├── libs │ └── nanohttpd-2.3.1.jar ├── src │ ├── main │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── absinthe │ │ │ │ └── kage │ │ │ │ ├── connect │ │ │ │ ├── tcp │ │ │ │ │ ├── Response.kt │ │ │ │ │ ├── Packet.kt │ │ │ │ │ ├── IPacketReader.kt │ │ │ │ │ ├── IPacketWriter.kt │ │ │ │ │ ├── PacketWriter.kt │ │ │ │ │ └── Request.kt │ │ │ │ ├── IProxy.kt │ │ │ │ ├── protocol │ │ │ │ │ ├── Config.kt │ │ │ │ │ ├── IProtocolHandler.kt │ │ │ │ │ ├── IpMessageProtocol.kt │ │ │ │ │ └── IpMessageConst.kt │ │ │ │ ├── Const.kt │ │ │ │ └── proxy │ │ │ │ │ ├── BaseProxy.kt │ │ │ │ │ └── RemoteControlProxy.kt │ │ │ │ ├── viewholder │ │ │ │ ├── model │ │ │ │ │ ├── CastItem.kt │ │ │ │ │ ├── ConnectItem.kt │ │ │ │ │ ├── ServiceRunningItem.kt │ │ │ │ │ └── DeviceItem.kt │ │ │ │ ├── SpacesItemDecoration.kt │ │ │ │ ├── WrappedLinearLayoutManager.kt │ │ │ │ ├── delegate │ │ │ │ │ ├── DeviceItemViewBinder.kt │ │ │ │ │ ├── CastItemViewBinder.kt │ │ │ │ │ ├── ConnectItemViewBinder.kt │ │ │ │ │ └── DeviceInfoItemViewBinder.kt │ │ │ │ └── HolderConstant.kt │ │ │ │ ├── device │ │ │ │ ├── heartbeat │ │ │ │ │ ├── ErrorResponse.kt │ │ │ │ │ ├── CancelBeatResponse.kt │ │ │ │ │ └── HeartbeatRequest.kt │ │ │ │ ├── model │ │ │ │ │ ├── VideoInfo.kt │ │ │ │ │ ├── AudioInfo.kt │ │ │ │ │ ├── DeviceConfig.kt │ │ │ │ │ └── DeviceInfo.kt │ │ │ │ ├── Command.kt │ │ │ │ ├── CommandBuilder.kt │ │ │ │ ├── KageObservable.kt │ │ │ │ ├── IDeviceObserver.kt │ │ │ │ ├── DeviceObserverImpl.kt │ │ │ │ └── cmd │ │ │ │ │ ├── SetPlayIndexCommand.kt │ │ │ │ │ ├── SetPlayStateCommand.kt │ │ │ │ │ ├── SetPlayStatusCommand.kt │ │ │ │ │ ├── PlayNextCommand.kt │ │ │ │ │ ├── PlayPreviousCommand.kt │ │ │ │ │ ├── InquiryAudioModeCommand.kt │ │ │ │ │ ├── SetAudioModeCommand.kt │ │ │ │ │ ├── SetPlayingPositionCommand.kt │ │ │ │ │ ├── InquiryDeviceInfoCommand.kt │ │ │ │ │ ├── StopCommand.kt │ │ │ │ │ ├── ResumePlayCommand.kt │ │ │ │ │ ├── InquiryDurationCommand.kt │ │ │ │ │ ├── InquiryPlayingPositionCommand.kt │ │ │ │ │ ├── SetDurationCommand.kt │ │ │ │ │ ├── InquiryPlayStateCommand.kt │ │ │ │ │ ├── InquiryPlayerStatusCommand.kt │ │ │ │ │ ├── PromptPhoneConnectedCommand.kt │ │ │ │ │ ├── HeartbeatCommand.kt │ │ │ │ │ ├── MediaPausePlayingCommand.kt │ │ │ │ │ ├── SeekToCommand.kt │ │ │ │ │ ├── ImageInfoCommand.kt │ │ │ │ │ ├── RemoteControlKeyCommand.kt │ │ │ │ │ └── DeviceRotationCommand.kt │ │ │ │ ├── manager │ │ │ │ └── GlobalManager.kt │ │ │ │ ├── utils │ │ │ │ ├── timber │ │ │ │ │ ├── ThreadAwareDebugTree.kt │ │ │ │ │ └── ReleaseTree.kt │ │ │ │ ├── StorageUtils.kt │ │ │ │ ├── ToastUtil.kt │ │ │ │ ├── NotificationUtils.kt │ │ │ │ └── AnimationUtil.kt │ │ │ │ ├── view │ │ │ │ ├── AlwaysMarqueeTextView.kt │ │ │ │ └── CategoryCardView.kt │ │ │ │ ├── media │ │ │ │ ├── Playback.kt │ │ │ │ └── audio │ │ │ │ │ ├── LocalMusic.kt │ │ │ │ │ └── MusicHelper.kt │ │ │ │ ├── viewmodel │ │ │ │ └── MusicViewModel.kt │ │ │ │ ├── KageApplication.kt │ │ │ │ └── BaseActivity.kt │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── pic_image.webp │ │ │ ├── pic_music.webp │ │ │ ├── pic_video.webp │ │ │ ├── ic_launcher.png │ │ │ ├── pic_rabbit.webp │ │ │ ├── ic_launcher_round.png │ │ │ └── pic_album_placeholder.webp │ │ │ ├── drawable-hdpi │ │ │ └── ic_stat_logo.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_stat_logo.png │ │ │ ├── drawable-xhdpi │ │ │ └── ic_stat_logo.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── pic_splash.png │ │ │ └── ic_stat_logo.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_stat_logo.png │ │ │ ├── values │ │ │ ├── ic_launcher_background.xml │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ │ ├── anim │ │ │ ├── anim_fade_in.xml │ │ │ └── anim_fade_out.xml │ │ │ ├── drawable │ │ │ ├── bg_circle_button.xml │ │ │ ├── selector_connect.xml │ │ │ ├── bg_video_bottom_bar_mask.xml │ │ │ ├── bg_card_mask.xml │ │ │ ├── ic_pause.xml │ │ │ ├── ic_skip_next.xml │ │ │ ├── ic_skip_previous.xml │ │ │ ├── ic_play_arrow.xml │ │ │ ├── ic_arrow_back.xml │ │ │ ├── ic_ring.xml │ │ │ ├── ic_info.xml │ │ │ ├── ic_done.xml │ │ │ ├── ic_no.xml │ │ │ ├── splash_drawable.xml │ │ │ ├── ic_album.xml │ │ │ ├── ic_delivery.xml │ │ │ ├── ic_main_connect.xml │ │ │ ├── ic_logo.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── ic_connected.xml │ │ │ └── ic_android_outline.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── menu │ │ │ └── main_menu.xml │ │ │ ├── values-night │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ │ ├── values-night-v26 │ │ │ └── styles.xml │ │ │ ├── values-v26 │ │ │ └── styles.xml │ │ │ ├── xml │ │ │ └── backup_rules.xml │ │ │ ├── layout │ │ │ ├── activity_receiver.xml │ │ │ ├── activity_video.xml │ │ │ ├── layout_category_card_view.xml │ │ │ ├── layout_receiver_loading.xml │ │ │ ├── view_video_player.xml │ │ │ ├── layout_music_seek_bar.xml │ │ │ ├── activity_main.xml │ │ │ ├── layout_empty_list_tip.xml │ │ │ ├── item_device.xml │ │ │ ├── item_cast_card.xml │ │ │ ├── item_connect_card.xml │ │ │ ├── activity_connect.xml │ │ │ ├── activity_music_list.xml │ │ │ ├── layout_video_seek_bar.xml │ │ │ ├── item_music.xml │ │ │ └── item_service_running_card.xml │ │ │ ├── layout-land │ │ │ └── layout_empty_list_tip.xml │ │ │ └── values-zh │ │ │ └── strings.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── absinthe │ │ │ └── kage │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── absinthe │ │ └── kage │ │ └── ExampleInstrumentedTest.java └── release │ ├── output.json │ └── output-metadata.json ├── matisse ├── gradle.properties ├── src │ └── main │ │ ├── res │ │ ├── drawable-hdpi │ │ │ ├── ic_gif.png │ │ │ ├── ic_empty_zhihu.png │ │ │ ├── ic_empty_dracula.png │ │ │ ├── ic_check_white_18dp.png │ │ │ ├── ic_preview_radio_off.webp │ │ │ ├── ic_preview_radio_on.webp │ │ │ ├── ic_arrow_drop_down_white_24dp.png │ │ │ └── ic_play_circle_outline_white_48dp.png │ │ ├── drawable-mdpi │ │ │ ├── ic_gif.png │ │ │ ├── ic_empty_zhihu.png │ │ │ ├── ic_empty_dracula.png │ │ │ ├── ic_check_white_18dp.png │ │ │ ├── ic_preview_radio_off.webp │ │ │ ├── ic_preview_radio_on.webp │ │ │ ├── ic_arrow_drop_down_white_24dp.png │ │ │ └── ic_play_circle_outline_white_48dp.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_gif.png │ │ │ ├── ic_empty_dracula.png │ │ │ ├── ic_empty_zhihu.png │ │ │ ├── ic_check_white_18dp.png │ │ │ ├── ic_preview_radio_off.webp │ │ │ ├── ic_preview_radio_on.webp │ │ │ ├── ic_arrow_drop_down_white_24dp.png │ │ │ └── ic_play_circle_outline_white_48dp.png │ │ ├── drawable-xxhdpi │ │ │ ├── ic_gif.png │ │ │ ├── ic_empty_zhihu.png │ │ │ ├── ic_empty_dracula.png │ │ │ ├── ic_check_white_18dp.png │ │ │ ├── ic_arrow_drop_down_white_24dp.png │ │ │ └── ic_play_circle_outline_white_48dp.png │ │ ├── drawable-xxxhdpi │ │ │ ├── ic_gif.png │ │ │ ├── ic_empty_zhihu.png │ │ │ ├── ic_empty_dracula.png │ │ │ ├── ic_check_white_18dp.png │ │ │ ├── ic_photo_camera_white_24dp.png │ │ │ ├── ic_arrow_drop_down_white_24dp.png │ │ │ └── ic_play_circle_outline_white_48dp.png │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ ├── attrs.xml │ │ │ ├── colors_dracula.xml │ │ │ ├── colors_zhihu.xml │ │ │ └── strings.xml │ │ ├── layout │ │ │ ├── media_grid_item.xml │ │ │ ├── fragment_media_selection.xml │ │ │ ├── fragment_preview_item.xml │ │ │ ├── photo_capture_item.xml │ │ │ └── media_grid_content.xml │ │ ├── color │ │ │ ├── zhihu_bottom_toolbar_apply.xml │ │ │ ├── dracula_bottom_toolbar_apply.xml │ │ │ ├── zhihu_bottom_toolbar_preview.xml │ │ │ ├── dracula_bottom_toolbar_preview.xml │ │ │ ├── dracula_preview_bottom_toolbar_apply.xml │ │ │ └── zhihu_preview_bottom_toolbar_apply.xml │ │ └── values-zh │ │ │ └── strings.xml │ │ ├── java │ │ └── com │ │ │ └── zhihu │ │ │ └── matisse │ │ │ ├── listener │ │ │ ├── OnChooseItemListener.java │ │ │ ├── OnCheckedListener.java │ │ │ ├── OnFragmentInteractionListener.java │ │ │ └── OnSelectedListener.java │ │ │ ├── internal │ │ │ ├── utils │ │ │ │ ├── Platform.java │ │ │ │ └── SingleMediaScanner.java │ │ │ ├── entity │ │ │ │ └── CaptureStrategy.java │ │ │ └── ui │ │ │ │ ├── widget │ │ │ │ ├── SquareFrameLayout.java │ │ │ │ ├── PreviewViewPager.java │ │ │ │ └── CheckRadioView.java │ │ │ │ └── SelectedPreviewActivity.java │ │ │ ├── engine │ │ │ └── impl │ │ │ │ └── PicassoEngine.java │ │ │ └── filter │ │ │ └── Filter.java │ │ └── AndroidManifest.xml └── proguard-rules.pro ├── settings.gradle ├── icons ├── playstore │ └── icon.png ├── play_store_banner.png ├── mipmap-hdpi │ └── ic_launcher.png ├── mipmap-mdpi │ └── ic_launcher.png ├── mipmap-xhdpi │ └── ic_launcher.png ├── mipmap-xxhdpi │ └── ic_launcher.png ├── mipmap-xxxhdpi │ └── ic_launcher.png └── LICENSE │ └── LICENSE.txt ├── source └── coolapk-badge.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── vcs.xml ├── smartfox_info.xml ├── encodings.xml ├── modules.xml ├── runConfigurations.xml ├── jarRepositories.xml └── navEditor.xml ├── config.gradle ├── README.md ├── gradle.properties └── .gitignore /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /matisse/gradle.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':matisse' 2 | rootProject.name='Kage' -------------------------------------------------------------------------------- /icons/playstore/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/playstore/icon.png -------------------------------------------------------------------------------- /source/coolapk-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/source/coolapk-badge.png -------------------------------------------------------------------------------- /app/libs/nanohttpd-2.3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/libs/nanohttpd-2.3.1.jar -------------------------------------------------------------------------------- /icons/play_store_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/play_store_banner.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /icons/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/icons/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/tcp/Response.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.tcp 2 | 3 | open class Response -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/model/CastItem.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.model 2 | 3 | class CastItem -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/model/ConnectItem.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.model 2 | 3 | class ConnectItem -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/pic_image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/pic_image.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/pic_music.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/pic_music.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/pic_video.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/pic_video.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_gif.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_gif.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_gif.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_stat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/drawable-hdpi/ic_stat_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_stat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/drawable-mdpi/ic_stat_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_stat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/drawable-xhdpi/ic_stat_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/pic_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/drawable-xxxhdpi/pic_splash.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/pic_rabbit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/pic_rabbit.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxhdpi/ic_gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxhdpi/ic_gif.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_gif.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_stat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/drawable-xxhdpi/ic_stat_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_stat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/drawable-xxxhdpi/ic_stat_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_empty_zhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_empty_zhihu.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_empty_zhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_empty_zhihu.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_empty_dracula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_empty_dracula.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_empty_dracula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_empty_dracula.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_empty_dracula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_empty_dracula.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_empty_zhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_empty_zhihu.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxhdpi/ic_empty_zhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxhdpi/ic_empty_zhihu.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_empty_zhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_empty_zhihu.png -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/tcp/Packet.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.tcp 2 | 3 | open class Packet { 4 | var data: String? = null 5 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/pic_album_placeholder.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/app/src/main/res/mipmap-xxxhdpi/pic_album_placeholder.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_check_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_check_white_18dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_check_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_check_white_18dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxhdpi/ic_empty_dracula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxhdpi/ic_empty_dracula.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_empty_dracula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_empty_dracula.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_preview_radio_off.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_preview_radio_off.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_preview_radio_on.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_preview_radio_on.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_preview_radio_off.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_preview_radio_off.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_preview_radio_on.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_preview_radio_on.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_check_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_check_white_18dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_preview_radio_off.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_off.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_preview_radio_on.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_on.webp -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxhdpi/ic_check_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxhdpi/ic_check_white_18dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_check_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_check_white_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFC107 4 | -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_arrow_drop_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_arrow_drop_down_white_24dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_arrow_drop_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_arrow_drop_down_white_24dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_arrow_drop_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_arrow_drop_down_white_24dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxhdpi/ic_arrow_drop_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxhdpi/ic_arrow_drop_down_white_24dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down_white_24dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png -------------------------------------------------------------------------------- /matisse/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobozhen/Kage/HEAD/matisse/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/tcp/IPacketReader.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.tcp 2 | 3 | interface IPacketReader { 4 | fun addRequest(request: Request) 5 | fun shutdown() 6 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/tcp/IPacketWriter.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.tcp 2 | 3 | interface IPacketWriter { 4 | fun writePacket(packet: Packet) 5 | fun shutdown() 6 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/heartbeat/ErrorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.heartbeat 2 | 3 | import com.absinthe.kage.connect.tcp.Response 4 | 5 | class ErrorResponse : Response() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/heartbeat/CancelBeatResponse.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.heartbeat 2 | 3 | import com.absinthe.kage.connect.tcp.Response 4 | 5 | class CancelBeatResponse : Response() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/model/ServiceRunningItem.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.model 2 | 3 | class ServiceRunningItem(running: Boolean) { 4 | var isServiceRunning: Boolean = running 5 | } -------------------------------------------------------------------------------- /.idea/smartfox_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/model/DeviceItem.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.model 2 | 3 | class DeviceItem(name: String = "", ip: String = "") { 4 | val deviceName = name 5 | val deviceIp = ip 6 | } -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_circle_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/IProxy.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect 2 | 3 | import com.absinthe.kage.device.Device 4 | 5 | interface IProxy { 6 | fun onDeviceConnected(device: Device) 7 | fun onDeviceDisconnected(device: Device) 8 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 07 13:52:11 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip 7 | -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/listener/OnChooseItemListener.java: -------------------------------------------------------------------------------- 1 | package com.zhihu.matisse.listener; 2 | 3 | public interface OnChooseItemListener { 4 | void onChoose(String itemUri); 5 | void onStop(); 6 | void onPreview(); 7 | void onNext(); 8 | void onNotConnect(); 9 | } 10 | -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/listener/OnCheckedListener.java: -------------------------------------------------------------------------------- 1 | package com.zhihu.matisse.listener; 2 | 3 | 4 | /** 5 | * when original is enabled , callback immediately when user check or uncheck original. 6 | */ 7 | public interface OnCheckedListener { 8 | void onCheck(boolean isChecked); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/protocol/Config.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.protocol 2 | 3 | object Config { 4 | const val VERSION = 1 //Protocol version 5 | const val ADDRESS = "127.0.0.1" //Server address 6 | const val PORT = 2025 //Server port 7 | const val HTTP_SERVER_PORT = 2026 //HTTP Server port 8 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_connect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/tcp/PacketWriter.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.tcp 2 | 3 | import com.absinthe.kage.connect.tcp.KageSocket.ISocketCallback 4 | import java.io.DataOutputStream 5 | 6 | class PacketWriter(out: DataOutputStream, socketCallback: ISocketCallback?) 7 | : AbstractPacketWriter(out, socketCallback) -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_video_bottom_bar_mask.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/heartbeat/HeartbeatRequest.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.heartbeat 2 | 3 | import com.absinthe.kage.connect.tcp.Request 4 | import com.absinthe.kage.device.cmd.HeartbeatCommand 5 | 6 | class HeartbeatRequest : Request() { 7 | 8 | init { 9 | data = HeartbeatCommand().pack() 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_card_mask.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skip_next.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skip_previous.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/model/VideoInfo.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.model 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageProtocol 4 | 5 | class VideoInfo { 6 | 7 | var url: String? = null 8 | var title: String? = null 9 | 10 | val info: String 11 | get() = url + IpMessageProtocol.DELIMITER + title 12 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | android = [ 3 | compileSdkVersion : 29, 4 | buildToolsVersion : '29.0.3', 5 | minSdkVersion : 21, 6 | targetSdkVersion : 29 7 | ] 8 | 9 | app_compat_version = '1.2.0' 10 | recyclerview_version = '1.1.0' 11 | glide_version = '4.11.0' 12 | lifecycle_version = '2.2.0' 13 | } -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/listener/OnFragmentInteractionListener.java: -------------------------------------------------------------------------------- 1 | package com.zhihu.matisse.listener; 2 | 3 | /** 4 | * PreViewItemFragment 和 BasePreViewActivity 通信的接口 ,为了方便拿到 ImageViewTouch 的点击事件 5 | */ 6 | public interface OnFragmentInteractionListener { 7 | /** 8 | * ImageViewTouch 被点击了 9 | */ 10 | void onClick(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/manager/GlobalManager.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.manager 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import com.absinthe.kage.media.audio.LocalMusic 5 | import java.util.ArrayList 6 | 7 | object GlobalManager { 8 | 9 | var isServiceRunning: MutableLiveData = MutableLiveData() 10 | var musicList: MutableList = ArrayList() 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/utils/timber/ThreadAwareDebugTree.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.utils.timber 2 | 3 | import timber.log.Timber.DebugTree 4 | 5 | open class ThreadAwareDebugTree : DebugTree() { 6 | 7 | override fun createStackElementTag(element: StackTraceElement): String? { 8 | //日志显示行号 9 | return super.createStackElementTag(element) + " (Line ${element.lineNumber})" 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFFFF 4 | #FF121212 5 | #FF121212 6 | #FF121212 7 | 8 | #FF888888 9 | #FFEEEEEE 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_ring.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.absinthe.kage", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "properties": [], 14 | "versionCode": 501, 15 | "versionName": "501", 16 | "enabled": true, 17 | "outputFile": "app-release.apk" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.absinthe.kage", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "properties": [], 14 | "versionCode": 501, 15 | "versionName": "0.5.0", 16 | "enabled": true, 17 | "outputFile": "app-release.apk" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/Const.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect 2 | 3 | import com.absinthe.kage.connect.protocol.Config 4 | 5 | object Const { 6 | const val LOCAL_IP_IN_AP = "192.168.43.1" 7 | const val BROADCAST_IP_IN_AP = "192.168.43.255" 8 | const val BROADCAST_IP_IN_WIFI = "255.255.255.255" 9 | const val HTTP_SERVER_FORMAT = "http://%s:${Config.HTTP_SERVER_PORT}" 10 | 11 | const val APP_CENTER_SECRET = "4b4faea6-9eed-4c30-a734-3fb9330da2cc" 12 | } -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/absinthe/kage/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/internal/utils/Platform.java: -------------------------------------------------------------------------------- 1 | package com.zhihu.matisse.internal.utils; 2 | 3 | import android.os.Build; 4 | 5 | /** 6 | * @author JoongWon Baik 7 | */ 8 | public class Platform { 9 | public static boolean hasICS() { 10 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; 11 | } 12 | 13 | public static boolean hasKitKat() { 14 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_no.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/Command.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageProtocol 4 | import com.absinthe.kage.device.client.Client 5 | 6 | abstract class Command protected constructor() { 7 | 8 | var cmd = 0 9 | 10 | abstract fun pack(): String 11 | abstract fun doWork(client: Client, received: String) 12 | abstract fun parseReceived(received: String): Boolean 13 | 14 | companion object { 15 | const val DELIMITER = IpMessageProtocol.DELIMITER 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/model/AudioInfo.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | class AudioInfo { 6 | 7 | @SerializedName("url") 8 | var url: String? = null //播放地址 9 | 10 | @SerializedName("name") 11 | var name: String? = null //歌曲名 12 | 13 | @SerializedName("artist") 14 | var artist: String? = null //演唱者 15 | 16 | @SerializedName("album") 17 | var album: String? = null //专辑 18 | 19 | @SerializedName("coverPath") 20 | var coverPath: String? = null //封面地址 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/CommandBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device 2 | 3 | class CommandBuilder { 4 | 5 | private val mStringBuilder: StringBuilder = StringBuilder() 6 | 7 | fun with(command: Command): CommandBuilder { 8 | mStringBuilder.append(command.cmd) 9 | return this 10 | } 11 | 12 | fun append(param: String?): CommandBuilder { 13 | mStringBuilder.append(Command.DELIMITER).append(param) 14 | return this 15 | } 16 | 17 | fun build(): String { 18 | return mStringBuilder.toString() 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/utils/timber/ReleaseTree.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.utils.timber 2 | 3 | import android.util.Log 4 | import timber.log.Timber 5 | 6 | class ReleaseTree : Timber.Tree() { 7 | 8 | override fun isLoggable(tag: String?, priority: Int): Boolean { 9 | return !(priority == Log.VERBOSE || priority == Log.DEBUG) 10 | } 11 | 12 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 13 | if (!isLoggable(tag, priority)) { 14 | return 15 | } 16 | super.log(priority, tag, message, t) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/utils/StorageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.utils 2 | 3 | import android.graphics.Bitmap 4 | import java.io.File 5 | import java.io.FileOutputStream 6 | import java.io.IOException 7 | 8 | object StorageUtils { 9 | 10 | @JvmStatic 11 | fun saveBitmap(bmp: Bitmap, file: File) { 12 | try { 13 | val out = FileOutputStream(file) 14 | bmp.compress(Bitmap.CompressFormat.PNG, 100, out) 15 | out.flush() 16 | out.close() 17 | } catch (e: IOException) { 18 | e.printStackTrace() 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/view/AlwaysMarqueeTextView.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.appcompat.widget.AppCompatTextView 6 | 7 | class AlwaysMarqueeTextView : AppCompatTextView { 8 | 9 | constructor(context: Context) : super(context) 10 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 11 | constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) 12 | 13 | override fun isFocused(): Boolean { 14 | return true 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/KageObservable.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device 2 | 3 | abstract class KageObservable { 4 | abstract fun register(observer: IDeviceObserver?) 5 | abstract fun unregister(observer: IDeviceObserver?) 6 | protected abstract fun notifyFindDevice(device: Device) 7 | protected abstract fun notifyLostDevice(device: Device) 8 | protected abstract fun notifyDeviceConnected(device: Device) 9 | protected abstract fun notifyDeviceDisconnect(device: Device) 10 | protected abstract fun notifyDeviceConnectFailed(device: Device, errorCode: Int, errorMessage: String?) 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/protocol/IProtocolHandler.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.protocol 2 | 3 | interface IProtocolHandler { 4 | 5 | fun handleSocketConnectedEvent() 6 | fun handleSocketMassage(msg: String) 7 | fun handleSocketDisConnectEvent() 8 | fun handleSocketConnectFail(errorCode: Int, e: Exception) 9 | fun handleSocketSendOrReceiveError() 10 | 11 | interface IProtocolHandleCallback { 12 | fun onProtocolConnected() 13 | fun onProtocolDisConnect() 14 | fun onProtocolConnectedFailed(errorCode: Int, e: Exception?) 15 | fun onProtocolSendOrReceiveError() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/IDeviceObserver.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device 2 | 3 | import com.absinthe.kage.device.model.DeviceInfo 4 | 5 | interface IDeviceObserver { 6 | fun onFindDevice(deviceInfo: DeviceInfo) 7 | fun onLostDevice(deviceInfo: DeviceInfo) 8 | fun onDeviceConnected(deviceInfo: DeviceInfo) 9 | fun onDeviceDisConnect(deviceInfo: DeviceInfo) 10 | fun onDeviceConnectFailed(deviceInfo: DeviceInfo, errorCode: Int, errorMessage: String?) 11 | fun onDeviceInfoChanged(deviceInfo: DeviceInfo) 12 | fun onDeviceNotice(deviceInfo: DeviceInfo) 13 | fun onDeviceConnecting(deviceInfo: DeviceInfo) 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_album.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/proxy/BaseProxy.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.proxy 2 | 3 | import com.absinthe.kage.connect.IProxy 4 | import com.absinthe.kage.device.Device 5 | 6 | const val MODE_IMAGE = 0 7 | const val MODE_AUDIO = 1 8 | const val MODE_VIDEO = 2 9 | 10 | open class BaseProxy : IProxy { 11 | var mDevice: Device? = null 12 | 13 | override fun onDeviceConnected(device: Device) { 14 | mDevice = device 15 | } 16 | 17 | override fun onDeviceDisconnected(device: Device) { 18 | mDevice = null 19 | } 20 | 21 | companion object { 22 | var CURRENT_MODE = MODE_IMAGE 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/model/DeviceConfig.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.model 2 | 3 | import com.absinthe.kage.connect.Const 4 | import com.absinthe.kage.connect.protocol.Config 5 | import com.absinthe.kage.utils.NetUtils 6 | import java.util.* 7 | 8 | class DeviceConfig { 9 | var name: String = "Unknown" 10 | var uuid: String = UUID.randomUUID().toString() 11 | var localHost: String = NetUtils.localAddress 12 | var broadcastHostInWifi: String = Const.BROADCAST_IP_IN_WIFI 13 | var broadcastHostInAp: String = Const.BROADCAST_IP_IN_AP 14 | var broadcastMonitorPort = Config.PORT 15 | var broadcastPort = Config.PORT 16 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night-v26/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/proxy/RemoteControlProxy.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.proxy 2 | 3 | import android.view.KeyEvent 4 | import com.absinthe.kage.device.cmd.RemoteControlKeyCommand 5 | 6 | object RemoteControlProxy : BaseProxy() { 7 | 8 | fun sendVolumeUpKeyAction() { 9 | sendKeyAction(KeyEvent.KEYCODE_VOLUME_UP) 10 | } 11 | 12 | fun sendVolumeDownKeyAction() { 13 | sendKeyAction(KeyEvent.KEYCODE_VOLUME_DOWN) 14 | } 15 | 16 | private fun sendKeyAction(key: Int) { 17 | mDevice?.let { 18 | it.sendCommand(RemoteControlKeyCommand().apply { 19 | keyCode = key 20 | }) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/media/Playback.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.media 2 | 3 | const val TYPE_LOCAL = 1 4 | const val TYPE_REMOTE = 2 5 | 6 | interface Playback { 7 | interface Callback { 8 | fun onCompletion() 9 | fun onError(error: String) 10 | fun onMediaMetadataChanged(localMedia: LocalMedia) 11 | fun onPlaybackStateChanged(state: Int) 12 | } 13 | 14 | val bufferPosition: Int 15 | val currentPosition: Int 16 | val duration: Int 17 | val state: Int 18 | 19 | fun pause() 20 | fun play() 21 | fun playMedia(localMedia: LocalMedia) 22 | fun seekTo(position: Int) 23 | fun setCallback(callback: Callback) 24 | fun stop(isStop: Boolean) 25 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v26/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delivery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/DeviceObserverImpl.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device 2 | 3 | import com.absinthe.kage.device.model.DeviceInfo 4 | 5 | open class DeviceObserverImpl : IDeviceObserver { 6 | override fun onFindDevice(deviceInfo: DeviceInfo) {} 7 | override fun onLostDevice(deviceInfo: DeviceInfo) {} 8 | override fun onDeviceConnected(deviceInfo: DeviceInfo) {} 9 | override fun onDeviceDisConnect(deviceInfo: DeviceInfo) {} 10 | override fun onDeviceConnectFailed(deviceInfo: DeviceInfo, errorCode: Int, errorMessage: String?) {} 11 | override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {} 12 | override fun onDeviceNotice(deviceInfo: DeviceInfo) {} 13 | override fun onDeviceConnecting(deviceInfo: DeviceInfo) {} 14 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_receiver.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /matisse/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Library/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | -dontwarn com.squareup.okhttp.** 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/utils/ToastUtil.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.utils 2 | 3 | import android.widget.Toast 4 | import androidx.annotation.StringRes 5 | import com.blankj.utilcode.util.Utils 6 | 7 | object ToastUtil { 8 | 9 | /** 10 | * make a toast via a string 11 | * 12 | * @param text a string text 13 | */ 14 | fun makeText(text: String) { 15 | Toast.makeText(Utils.getApp().applicationContext, text, Toast.LENGTH_SHORT).show() 16 | } 17 | 18 | /** 19 | * make a toast via a resource id 20 | * 21 | * @param resId a string resource id 22 | */ 23 | @JvmStatic 24 | fun makeText(@StringRes resId: Int) { 25 | Toast.makeText(Utils.getApp().applicationContext, Utils.getApp().applicationContext.getText(resId), Toast.LENGTH_SHORT).show() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/SetPlayIndexCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class SetPlayIndexCommand : Command() { 9 | var index = 0 10 | 11 | override fun pack(): String { 12 | return CommandBuilder() 13 | .with(this) 14 | .append(index.toString()) 15 | .build() 16 | } 17 | 18 | override fun doWork(client: Client, received: String) {} 19 | 20 | override fun parseReceived(received: String): Boolean { 21 | return false 22 | } 23 | 24 | init { 25 | cmd = IpMessageConst.MEDIA_SET_PLAY_INDEX 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/SetPlayStateCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class SetPlayStateCommand : Command() { 9 | var stateCode = 0 10 | 11 | override fun pack(): String { 12 | return CommandBuilder() 13 | .with(this) 14 | .append(stateCode.toString()) 15 | .build() 16 | } 17 | 18 | override fun doWork(client: Client, received: String) {} 19 | 20 | override fun parseReceived(received: String): Boolean { 21 | return false 22 | } 23 | 24 | init { 25 | cmd = IpMessageConst.MEDIA_SET_PLAYING_STATE 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/SetPlayStatusCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class SetPlayStatusCommand : Command() { 9 | var statusCode = 0 10 | 11 | override fun pack(): String { 12 | return CommandBuilder() 13 | .with(this) 14 | .append(statusCode.toString()) 15 | .build() 16 | } 17 | 18 | override fun doWork(client: Client, received: String) {} 19 | 20 | override fun parseReceived(received: String): Boolean { 21 | return false 22 | } 23 | 24 | init { 25 | cmd = IpMessageConst.MEDIA_SET_PLAYER_STATUS 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_main_connect.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/SpacesItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | import androidx.recyclerview.widget.RecyclerView.ItemDecoration 7 | 8 | class SpacesItemDecoration(private val space: Int) : ItemDecoration() { 9 | 10 | override fun getItemOffsets(outRect: Rect, view: View, 11 | parent: RecyclerView, state: RecyclerView.State) { 12 | outRect.apply { 13 | left = space 14 | right = space 15 | bottom = space 16 | } 17 | 18 | // Add top margin only for the first item to avoid double space between items 19 | if (parent.getChildLayoutPosition(view) == 0) { 20 | outRect.top = space 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/PlayNextCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class PlayNextCommand : Command() { 9 | 10 | override fun pack(): String { 11 | return CommandBuilder() 12 | .with(this) 13 | .append(MESSAGE) 14 | .build() 15 | } 16 | 17 | override fun doWork(client: Client, received: String) {} 18 | 19 | override fun parseReceived(received: String): Boolean { 20 | return false 21 | } 22 | 23 | companion object { 24 | const val MESSAGE = "PLAY_NEXT" 25 | } 26 | 27 | init { 28 | cmd = IpMessageConst.MEDIA_PLAY_NEXT 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/model/DeviceInfo.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.model 2 | 3 | import com.absinthe.kage.connect.protocol.Config 4 | import com.absinthe.kage.connect.protocol.IpMessageConst 5 | 6 | class DeviceInfo { 7 | 8 | var name: String = "Unknown" 9 | var ip: String = Config.ADDRESS 10 | var protocolVersion: String = IpMessageConst.VERSION.toString() 11 | var functionCode: String = "" 12 | var isConnected = false 13 | var state = STATE_IDLE 14 | 15 | fun setStateConnecting() { 16 | state = STATE_CONNECTING 17 | } 18 | 19 | override fun toString(): String { 20 | return "DeviceInfo: Name = $name, IP = $ip" 21 | } 22 | 23 | companion object { 24 | const val STATE_IDLE = 0 //未连接 25 | const val STATE_CONNECTING = 1 //连接中 26 | const val STATE_CONNECTED = 2 //已连接 27 | } 28 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kage 2 | A real-time displayed multimedia projection system based on Android. 3 | 4 | ## What's this? 5 | This application is my graduation project. First and foremost, I would like to express my sincere gratitude to my supervisor, Professor Xu, for his intellectual guidance, invaluable instructions and comments on my thesis. It is with his valuable assistance that I have finally accomplished this thesis. 6 | 7 | Kage(かげ), means "shadow", which means that the sending end and the receiving end are consistent like an object and its shadow. The innovations of this application are as follows: 8 | 9 | - A real-time displayed multimedia projection system 10 | - Adapt to different sizes and different models of devices 11 | - Integrate the sending end and receiving end in the same application 12 | 13 | ## Download 14 | [![CoolApk](/source/coolapk-badge.png)](https://www.coolapk.com/apk/com.absinthe.kage) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/PlayPreviousCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class PlayPreviousCommand : Command() { 9 | 10 | override fun pack(): String { 11 | return CommandBuilder() 12 | .with(this) 13 | .append(MESSAGE) 14 | .build() 15 | } 16 | 17 | override fun doWork(client: Client, received: String) {} 18 | 19 | override fun parseReceived(received: String): Boolean { 20 | return false 21 | } 22 | 23 | companion object { 24 | const val MESSAGE = "PLAY_PRE" 25 | } 26 | 27 | init { 28 | cmd = IpMessageConst.MEDIA_PLAY_PREVIOUS 29 | } 30 | } -------------------------------------------------------------------------------- /matisse/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 48dp 19 | 4dp 20 | 21 | 72dp 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/WrappedLinearLayoutManager.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder 2 | 3 | import android.content.Context 4 | import androidx.recyclerview.widget.LinearLayoutManager 5 | import androidx.recyclerview.widget.RecyclerView 6 | import androidx.recyclerview.widget.RecyclerView.Recycler 7 | import timber.log.Timber 8 | 9 | class WrappedLinearLayoutManager : LinearLayoutManager { 10 | 11 | constructor(context: Context?) : super(context) 12 | 13 | constructor(context: Context?, @RecyclerView.Orientation orientation: Int) : super(context, orientation, false) 14 | 15 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { 16 | try { 17 | super.onLayoutChildren(recycler, state) 18 | } catch (e: IndexOutOfBoundsException) { 19 | Timber.e("encounter an IndexOutOfBoundsException") 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/absinthe/kage/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.absinthe.kage", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/InquiryAudioModeCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class InquiryAudioModeCommand : Command() { 9 | 10 | override fun pack(): String { 11 | return CommandBuilder() 12 | .with(this) 13 | .append(MESSAGE) 14 | .build() 15 | } 16 | 17 | override fun doWork(client: Client, received: String) {} 18 | 19 | override fun parseReceived(received: String): Boolean { 20 | return false 21 | } 22 | 23 | companion object { 24 | const val MESSAGE = "INQUIRY_AUDIO_MODE" 25 | } 26 | 27 | init { 28 | cmd = IpMessageConst.RESPONSE_SET_AUDIO_MODE 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/SetAudioModeCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class SetAudioModeCommand : Command() { 9 | var mode = MODE_ 10 | 11 | override fun pack(): String { 12 | return CommandBuilder() 13 | .with(this) 14 | .append(mode.toString()) 15 | .build() 16 | } 17 | 18 | override fun doWork(client: Client, received: String) {} 19 | 20 | override fun parseReceived(received: String): Boolean { 21 | return false 22 | } 23 | 24 | companion object { 25 | const val MODE_ = 0 26 | } 27 | 28 | init { 29 | cmd = IpMessageConst.MEDIA_SET_AUDIO_MODE 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/SetPlayingPositionCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | 8 | class SetPlayingPositionCommand : Command() { 9 | var position = 0 10 | 11 | override fun pack(): String { 12 | return CommandBuilder() 13 | .with(this) 14 | .append(position.toString()) 15 | .build() 16 | } 17 | 18 | override fun doWork(client: Client, received: String) {} 19 | 20 | override fun parseReceived(received: String): Boolean { 21 | return false 22 | } 23 | 24 | companion object { 25 | const val LENGTH = 2 26 | } 27 | 28 | init { 29 | cmd = IpMessageConst.RESPONSE_SET_PLAYBACK_PROGRESS 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/tcp/Request.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.tcp 2 | 3 | import com.absinthe.kage.device.heartbeat.ErrorResponse 4 | import timber.log.Timber 5 | import java.util.concurrent.ArrayBlockingQueue 6 | import java.util.concurrent.TimeUnit 7 | 8 | open class Request : Packet() { 9 | 10 | var id: String = System.currentTimeMillis().toString() 11 | private val responses = ArrayBlockingQueue(1) 12 | 13 | fun setResponse(response: Response) { 14 | try { 15 | responses.put(response) 16 | } catch (e: InterruptedException) { 17 | Timber.e(e) 18 | } 19 | } 20 | 21 | fun waitResponse(timeout: Int): Response { 22 | return try { 23 | responses.poll(timeout.toLong(), TimeUnit.MILLISECONDS) 24 | } catch (e: InterruptedException) { 25 | e.printStackTrace() 26 | ErrorResponse() 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /matisse/src/main/res/layout/media_grid_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /matisse/src/main/res/color/zhihu_bottom_toolbar_apply.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /matisse/src/main/res/color/dracula_bottom_toolbar_apply.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /matisse/src/main/res/color/zhihu_bottom_toolbar_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewmodel/MusicViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewmodel 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.MutableLiveData 7 | import com.absinthe.kage.media.audio.LocalMusic 8 | import com.absinthe.kage.media.audio.MusicHelper 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.GlobalScope 11 | import kotlinx.coroutines.launch 12 | import kotlinx.coroutines.withContext 13 | 14 | class MusicViewModel(application: Application) : AndroidViewModel(application) { 15 | 16 | val musicList = MutableLiveData>() 17 | 18 | fun loadMusic(context: Context) { 19 | GlobalScope.launch(Dispatchers.IO) { 20 | val list = MusicHelper.getAllLocalMusic(context) 21 | withContext(Dispatchers.Main) { 22 | musicList.setValue(list as MutableList) 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /matisse/src/main/res/color/dracula_bottom_toolbar_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /matisse/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #CC000000 20 | #61FFFFFF 21 | #00000000 22 | 23 | #FF0077D9 24 | #FFE3170D 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/utils/NotificationUtils.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.utils 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import android.os.Build 7 | import com.absinthe.kage.R 8 | 9 | object NotificationUtils { 10 | 11 | const val TCP_CHANNEL_ID = "tcp_channel" 12 | 13 | @JvmStatic 14 | fun createTCPChannel(context: Context) { 15 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 16 | val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 17 | val name = context.getText(R.string.notification_channel_tcp) 18 | val importance = NotificationManager.IMPORTANCE_LOW 19 | val mChannel = NotificationChannel(TCP_CHANNEL_ID, name, importance) 20 | 21 | mChannel.setShowBadge(false) 22 | mChannel.setSound(null, null) 23 | manager.createNotificationChannel(mChannel) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /matisse/src/main/res/color/dracula_preview_bottom_toolbar_apply.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /matisse/src/main/res/color/zhihu_preview_bottom_toolbar_apply.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_video.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 17 | 18 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/view/CategoryCardView.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.LayoutInflater 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import com.absinthe.kage.R 9 | import com.google.android.material.card.MaterialCardView 10 | 11 | class CategoryCardView : MaterialCardView { 12 | 13 | constructor(context: Context?) : super(context) 14 | 15 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 16 | LayoutInflater.from(context).inflate(R.layout.layout_category_card_view, this) 17 | context.obtainStyledAttributes(attrs, R.styleable.CategoryCardView).apply { 18 | findViewById(R.id.tv_title)?.text = getString(R.styleable.CategoryCardView_categoryTitle) 19 | findViewById(R.id.image)?.setImageResource(getResourceId(R.styleable.CategoryCardView_categoryImage, 0)) 20 | recycle() 21 | } 22 | 23 | isClickable = true 24 | isFocusable = true 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/KageApplication.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage 2 | 3 | import android.app.Application 4 | import com.absinthe.kage.connect.Const 5 | import com.absinthe.kage.service.TCPService 6 | import com.absinthe.kage.utils.timber.ReleaseTree 7 | import com.absinthe.kage.utils.timber.ThreadAwareDebugTree 8 | import com.blankj.utilcode.util.ServiceUtils 9 | import com.microsoft.appcenter.AppCenter 10 | import com.microsoft.appcenter.analytics.Analytics 11 | import com.microsoft.appcenter.crashes.Crashes 12 | import timber.log.Timber 13 | 14 | class KageApplication : Application() { 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | 19 | if (BuildConfig.DEBUG) { 20 | Timber.plant(ThreadAwareDebugTree()) 21 | } else { 22 | Timber.plant(ReleaseTree()) 23 | AppCenter.start(this, Const.APP_CENTER_SECRET, 24 | Analytics::class.java, Crashes::class.java) 25 | } 26 | 27 | if (!ServiceUtils.isServiceRunning(TCPService::class.java)) { 28 | TCPService.start(this) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFC107 4 | #FFFFA000 5 | #FFFFD740 6 | #FF202020 7 | #FFFFFFFF 8 | 9 | #FFFFFFFF 10 | #FF000000 11 | #FF888888 12 | #FF666666 13 | #FFBBBBBB 14 | #FF4CB1FA 15 | #FF99D48D 16 | #FFFAC14C 17 | #FFE57373 18 | #FFBBBBBB 19 | 20 | #FFDDDDDD 21 | #FF737373 22 | 23 | #FFFFC107 24 | #FF232323 25 | #4C000000 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_category_card_view.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_receiver_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Zhihu Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.zhihu.matisse.listener; 18 | 19 | import android.net.Uri; 20 | import androidx.annotation.NonNull; 21 | 22 | import java.util.List; 23 | 24 | public interface OnSelectedListener { 25 | /** 26 | * @param uriList the selected item {@link Uri} list. 27 | * @param pathList the selected item file path list. 28 | */ 29 | void onSelected(@NonNull List uriList, @NonNull List pathList); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_video_player.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/InquiryDeviceInfoCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.DeviceManager.config 7 | import com.absinthe.kage.device.client.Client 8 | import java.io.IOException 9 | 10 | class InquiryDeviceInfoCommand : Command() { 11 | 12 | var phoneName: String? = null 13 | 14 | override fun pack(): String { 15 | return CommandBuilder() 16 | .with(this) 17 | .append(phoneName) 18 | .build() 19 | } 20 | 21 | override fun doWork(client: Client, received: String) { 22 | try { 23 | phoneName = config.name 24 | client.writeToStream(pack()) 25 | } catch (e: IOException) { 26 | e.printStackTrace() 27 | client.offline() 28 | } 29 | } 30 | 31 | override fun parseReceived(received: String): Boolean { 32 | return false 33 | } 34 | 35 | init { 36 | cmd = IpMessageConst.GET_DEVICE_INFO 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/delegate/DeviceItemViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.delegate 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.absinthe.kage.R 9 | import com.absinthe.kage.viewholder.model.DeviceItem 10 | import com.drakeet.multitype.ItemViewBinder 11 | 12 | class DeviceItemViewBinder : ItemViewBinder() { 13 | 14 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder { 15 | val root = inflater.inflate(R.layout.item_device_card, parent, false) 16 | return ViewHolder(root) 17 | } 18 | 19 | override fun onBindViewHolder(holder: ViewHolder, item: DeviceItem) { 20 | holder.deviceName.text = item.deviceName 21 | holder.deviceIp.text = item.deviceIp 22 | } 23 | 24 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 25 | val deviceName: TextView = itemView.findViewById(R.id.tv_device_name) 26 | val deviceIp: TextView = itemView.findViewById(R.id.tv_device_ip) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 60dp 7 | 70dp 8 | 32dp 9 | 10 | 300dp 11 | 60dp 12 | 5dp 13 | 30dp 14 | 30dp 15 | 16 | 200dp 17 | 3dp 18 | 5dp 19 | 20dp 20 | 21 | 45dp 22 | 10dp 23 | 5dp 24 | 20dp 25 | 30dp 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/StopCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import android.content.Intent 4 | import com.absinthe.kage.connect.protocol.IpMessageConst 5 | import com.absinthe.kage.device.Command 6 | import com.absinthe.kage.device.CommandBuilder 7 | import com.absinthe.kage.device.client.Client 8 | import com.absinthe.kage.ui.receiver.ReceiverActivity 9 | 10 | class StopCommand : Command() { 11 | 12 | override fun pack(): String { 13 | return CommandBuilder() 14 | .with(this) 15 | .append(STOP) 16 | .build() 17 | } 18 | 19 | override fun doWork(client: Client, received: String) { 20 | val stopIntent = Intent(client.context, ReceiverActivity::class.java) 21 | stopIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 22 | stopIntent.putExtra(ReceiverActivity.EXTRA_IMAGE_URI, ReceiverActivity.EXTRA_FINISH) 23 | client.context.startActivity(stopIntent) 24 | } 25 | 26 | override fun parseReceived(received: String): Boolean { 27 | return false 28 | } 29 | 30 | companion object { 31 | private const val STOP = "STOP" 32 | } 33 | 34 | init { 35 | cmd = IpMessageConst.MEDIA_STOP 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /icons/LICENSE/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Android Material Icon Generator License 2 | ================================ 3 | 4 | Icons generated with the Android Material Icon Generator come with the Creative Common 5 | Attribution 4.0 International License (CC-BY 4.0). You are free to change, 6 | combine and sell any of the icons as you please. Attribution would be great, 7 | but is not strictly required. 8 | 9 | This text only applies to the icons (.zip file) you download from the icon 10 | generator. The software behind the generator has its own license 11 | (https://www.apache.org/licenses/LICENSE-2.0). See the GitHub repository for 12 | details (https://github.com/Maddoc42/Android-Material-Icon-Generator). 13 | 14 | 15 | 16 | Google Material Icons License 17 | ============================= 18 | 19 | (Copied from https://github.com/google/material-design-icons) 20 | We have made these icons available for you to incorporate them into your 21 | products under the Creative Common Attribution 4.0 International License (CC-BY 22 | 4.0, https://creativecommons.org/licenses/by/4.0/). Feel free to remix and 23 | re-share these icons and documentation in your products. We'd love attribution 24 | in your app's *about* screen, but it's not required. The only thing we ask is 25 | that you not re-sell the icons themselves. -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Zhihu Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zhihu.matisse.internal.entity; 17 | 18 | public class CaptureStrategy { 19 | 20 | public final boolean isPublic; 21 | public final String authority; 22 | public final String directory; 23 | 24 | public CaptureStrategy(boolean isPublic, String authority) { 25 | this(isPublic, authority, null); 26 | } 27 | 28 | public CaptureStrategy(boolean isPublic, String authority, String directory) { 29 | this.isPublic = isPublic; 30 | this.authority = authority; 31 | this.directory = directory; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/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 generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/ResumePlayCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.connect.proxy.BaseProxy 5 | import com.absinthe.kage.connect.proxy.MODE_AUDIO 6 | import com.absinthe.kage.connect.proxy.MODE_VIDEO 7 | import com.absinthe.kage.device.Command 8 | import com.absinthe.kage.device.CommandBuilder 9 | import com.absinthe.kage.device.client.Client 10 | import com.absinthe.kage.media.audio.AudioPlayer 11 | import com.absinthe.kage.media.video.LocalVideoPlayback 12 | import timber.log.Timber 13 | 14 | class ResumePlayCommand : Command() { 15 | 16 | override fun pack(): String { 17 | return CommandBuilder() 18 | .with(this) 19 | .build() 20 | } 21 | 22 | override fun doWork(client: Client, received: String) { 23 | if (BaseProxy.CURRENT_MODE == MODE_AUDIO) { 24 | AudioPlayer.play() 25 | } else if (BaseProxy.CURRENT_MODE == MODE_VIDEO) { 26 | LocalVideoPlayback.INSTANCE?.play() 27 | } 28 | } 29 | 30 | override fun parseReceived(received: String): Boolean { 31 | return false 32 | } 33 | 34 | init { 35 | cmd = IpMessageConst.MEDIA_RESUME_PLAY 36 | } 37 | } -------------------------------------------------------------------------------- /matisse/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/InquiryDurationCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | import com.absinthe.kage.media.audio.AudioPlayer 8 | import java.io.IOException 9 | 10 | class InquiryDurationCommand : Command() { 11 | 12 | override fun pack(): String { 13 | return CommandBuilder() 14 | .with(this) 15 | .append(INQUIRY_MESSAGE) 16 | .build() 17 | } 18 | 19 | override fun doWork(client: Client, received: String) { 20 | val duration = AudioPlayer.duration 21 | val command = SetDurationCommand() 22 | command.duration = duration 23 | 24 | try { 25 | client.writeToStream(command.pack()) 26 | } catch (e: IOException) { 27 | e.printStackTrace() 28 | } 29 | } 30 | 31 | override fun parseReceived(received: String): Boolean { 32 | return false 33 | } 34 | 35 | companion object { 36 | const val INQUIRY_MESSAGE = "INQUIRY_DURATION" 37 | } 38 | 39 | init { 40 | cmd = IpMessageConst.MEDIA_GET_DURATION 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/InquiryPlayingPositionCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.device.Command 5 | import com.absinthe.kage.device.CommandBuilder 6 | import com.absinthe.kage.device.client.Client 7 | import com.absinthe.kage.media.audio.AudioPlayer.currentPosition 8 | import java.io.IOException 9 | 10 | class InquiryPlayingPositionCommand : Command() { 11 | 12 | override fun pack(): String { 13 | return CommandBuilder() 14 | .with(this) 15 | .append(MESSAGE) 16 | .build() 17 | } 18 | 19 | override fun doWork(client: Client, received: String) { 20 | val position = currentPosition 21 | val command = SetPlayingPositionCommand() 22 | command.position = position 23 | 24 | try { 25 | client.writeToStream(command.pack()) 26 | } catch (e: IOException) { 27 | e.printStackTrace() 28 | } 29 | } 30 | 31 | override fun parseReceived(received: String): Boolean { 32 | return false 33 | } 34 | 35 | companion object { 36 | const val MESSAGE = "INQUIRY_PLAYING_POSITION" 37 | } 38 | 39 | init { 40 | cmd = IpMessageConst.MEDIA_GET_PLAYING_POSITION 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/delegate/CastItemViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.delegate 2 | 3 | import android.content.Intent 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.absinthe.kage.R 9 | import com.absinthe.kage.ui.sender.SenderActivity 10 | import com.absinthe.kage.viewholder.HolderConstant 11 | import com.absinthe.kage.viewholder.model.CastItem 12 | import com.drakeet.multitype.ItemViewBinder 13 | 14 | class CastItemViewBinder : ItemViewBinder() { 15 | 16 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder { 17 | val root = inflater.inflate(R.layout.item_cast_card, parent, false) 18 | return ViewHolder(root) 19 | } 20 | 21 | override fun onBindViewHolder(holder: ViewHolder, item: CastItem) { 22 | holder.itemView.setOnClickListener { 23 | val intent = Intent(holder.itemView.context, SenderActivity::class.java) 24 | holder.itemView.context.startActivity(intent) 25 | } 26 | 27 | holder.itemView.setOnTouchListener(HolderConstant.onTouchListener) 28 | } 29 | 30 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 31 | } -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/internal/utils/SingleMediaScanner.java: -------------------------------------------------------------------------------- 1 | package com.zhihu.matisse.internal.utils; 2 | 3 | import android.content.Context; 4 | import android.media.MediaScannerConnection; 5 | import android.net.Uri; 6 | 7 | /** 8 | * @author 工藤 9 | * @email gougou@16fan.com 10 | * create at 2018年10月23日12:17:59 11 | * description:媒体扫描 12 | */ 13 | public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient { 14 | 15 | private MediaScannerConnection mMsc; 16 | private String mPath; 17 | private ScanListener mListener; 18 | 19 | public interface ScanListener { 20 | 21 | /** 22 | * scan finish 23 | */ 24 | void onScanFinish(); 25 | } 26 | 27 | public SingleMediaScanner(Context context, String mPath, ScanListener mListener) { 28 | this.mPath = mPath; 29 | this.mListener = mListener; 30 | this.mMsc = new MediaScannerConnection(context, this); 31 | this.mMsc.connect(); 32 | } 33 | 34 | @Override public void onMediaScannerConnected() { 35 | mMsc.scanFile(mPath, null); 36 | } 37 | 38 | @Override public void onScanCompleted(String mPath, Uri mUri) { 39 | mMsc.disconnect(); 40 | if (mListener != null) { 41 | mListener.onScanFinish(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareFrameLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Zhihu Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zhihu.matisse.internal.ui.widget; 17 | 18 | import android.content.Context; 19 | import android.util.AttributeSet; 20 | import android.widget.FrameLayout; 21 | 22 | public class SquareFrameLayout extends FrameLayout { 23 | 24 | public SquareFrameLayout(Context context) { 25 | super(context); 26 | } 27 | 28 | public SquareFrameLayout(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | } 31 | 32 | @Override 33 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 34 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/viewholder/delegate/ConnectItemViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.viewholder.delegate 2 | 3 | import android.content.Intent 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.absinthe.kage.R 9 | import com.absinthe.kage.ui.connect.ConnectActivity 10 | import com.absinthe.kage.viewholder.HolderConstant 11 | import com.absinthe.kage.viewholder.model.ConnectItem 12 | import com.drakeet.multitype.ItemViewBinder 13 | 14 | class ConnectItemViewBinder : ItemViewBinder() { 15 | 16 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder { 17 | val root = inflater.inflate(R.layout.item_connect_card, parent, false) 18 | return ViewHolder(root) 19 | } 20 | 21 | override fun onBindViewHolder(holder: ViewHolder, item: ConnectItem) { 22 | holder.itemView.setOnClickListener { 23 | val intent = Intent(holder.itemView.context, ConnectActivity::class.java) 24 | holder.itemView.context.startActivity(intent) 25 | } 26 | 27 | holder.itemView.setOnTouchListener(HolderConstant.onTouchListener) 28 | } 29 | 30 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 31 | } -------------------------------------------------------------------------------- /matisse/src/main/res/layout/fragment_media_selection.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/SetDurationCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.connect.protocol.IpMessageProtocol 5 | import com.absinthe.kage.device.Command 6 | import com.absinthe.kage.device.CommandBuilder 7 | import com.absinthe.kage.device.client.Client 8 | 9 | class SetDurationCommand : Command() { 10 | var duration = 0 11 | 12 | override fun pack(): String { 13 | return CommandBuilder() 14 | .with(this) 15 | .append(duration.toString()) 16 | .build() 17 | } 18 | 19 | override fun doWork(client: Client, received: String) {} 20 | 21 | override fun parseReceived(received: String): Boolean { 22 | val splits = received.split(IpMessageProtocol.DELIMITER).toTypedArray() 23 | 24 | return if (splits.size == LENGTH) { 25 | try { 26 | duration = splits[1].toInt() 27 | true 28 | } catch (e: NumberFormatException) { 29 | e.printStackTrace() 30 | false 31 | } 32 | } else { 33 | false 34 | } 35 | } 36 | 37 | companion object { 38 | const val LENGTH = 2 39 | } 40 | 41 | init { 42 | cmd = IpMessageConst.RESPONSE_SET_MEDIA_DURATION 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_music_seek_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 22 | 23 | 31 | 32 | -------------------------------------------------------------------------------- /matisse/src/main/res/layout/fragment_preview_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 25 | 26 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | 24 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/InquiryPlayStateCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.connect.proxy.BaseProxy 5 | import com.absinthe.kage.connect.proxy.MODE_AUDIO 6 | import com.absinthe.kage.device.Command 7 | import com.absinthe.kage.device.CommandBuilder 8 | import com.absinthe.kage.device.client.Client 9 | import com.absinthe.kage.media.audio.AudioPlayer 10 | import com.absinthe.kage.media.video.LocalVideoPlayback 11 | import java.io.IOException 12 | 13 | class InquiryPlayStateCommand : Command() { 14 | 15 | override fun pack(): String { 16 | return CommandBuilder() 17 | .with(this) 18 | .build() 19 | } 20 | 21 | override fun doWork(client: Client, received: String) { 22 | val command = SetPlayStateCommand().apply { 23 | stateCode = if (BaseProxy.CURRENT_MODE == MODE_AUDIO) 24 | AudioPlayer.playState 25 | else 26 | LocalVideoPlayback.INSTANCE?.state ?: 0 27 | } 28 | 29 | try { 30 | client.writeToStream(command.pack()) 31 | } catch (e: IOException) { 32 | e.printStackTrace() 33 | client.offline() 34 | } 35 | } 36 | 37 | override fun parseReceived(received: String): Boolean { 38 | return true 39 | } 40 | 41 | init { 42 | cmd = IpMessageConst.MEDIA_GET_PLAYING_STATE 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/InquiryPlayerStatusCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.connect.proxy.BaseProxy 5 | import com.absinthe.kage.connect.proxy.MODE_AUDIO 6 | import com.absinthe.kage.device.Command 7 | import com.absinthe.kage.device.CommandBuilder 8 | import com.absinthe.kage.device.client.Client 9 | import com.absinthe.kage.media.audio.AudioPlayer 10 | import com.absinthe.kage.media.video.LocalVideoPlayback 11 | import java.io.IOException 12 | 13 | class InquiryPlayerStatusCommand : Command() { 14 | 15 | override fun pack(): String { 16 | return CommandBuilder() 17 | .with(this) 18 | .build() 19 | } 20 | 21 | override fun doWork(client: Client, received: String) { 22 | val command = SetPlayStatusCommand().apply { 23 | statusCode = if (BaseProxy.CURRENT_MODE == MODE_AUDIO) 24 | AudioPlayer.playState 25 | else 26 | LocalVideoPlayback.INSTANCE?.state ?: 0 27 | } 28 | 29 | try { 30 | client.writeToStream(command.pack()) 31 | } catch (e: IOException) { 32 | e.printStackTrace() 33 | client.offline() 34 | } 35 | } 36 | 37 | override fun parseReceived(received: String): Boolean { 38 | return true 39 | } 40 | 41 | init { 42 | cmd = IpMessageConst.MEDIA_GET_PLAYER_STATUS 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/PromptPhoneConnectedCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.connect.protocol.IpMessageProtocol 5 | import com.absinthe.kage.device.Command 6 | import com.absinthe.kage.device.CommandBuilder 7 | import com.absinthe.kage.device.client.Client 8 | 9 | class PromptPhoneConnectedCommand : Command() { 10 | 11 | var phoneName: String? = null 12 | var localIp: String? = null 13 | 14 | override fun pack(): String { 15 | return CommandBuilder() 16 | .with(this) 17 | .append(phoneName) 18 | .append(localIp) 19 | .build() 20 | } 21 | 22 | override fun doWork(client: Client, received: String) { 23 | if (parseReceived(received)) { 24 | client.deviceInfo.name = phoneName!! 25 | client.deviceInfo.ip = localIp!! 26 | } 27 | } 28 | 29 | override fun parseReceived(received: String): Boolean { 30 | val splits = received.split(IpMessageProtocol.DELIMITER).toTypedArray() 31 | 32 | return if (splits.size == LENGTH) { 33 | phoneName = splits[1] 34 | localIp = splits[2] 35 | true 36 | } else { 37 | false 38 | } 39 | } 40 | 41 | companion object { 42 | const val LENGTH = 3 43 | } 44 | 45 | init { 46 | cmd = IpMessageConst.PROMPT_PHONE_CONNECT 47 | } 48 | } -------------------------------------------------------------------------------- /matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Zhihu Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.zhihu.matisse.internal.ui.widget; 17 | 18 | import android.content.Context; 19 | import androidx.viewpager.widget.ViewPager; 20 | import android.util.AttributeSet; 21 | import android.view.View; 22 | 23 | import it.sephiroth.android.library.imagezoom.ImageViewTouch; 24 | 25 | public class PreviewViewPager extends ViewPager { 26 | 27 | public PreviewViewPager(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | @Override 32 | protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 33 | if (v instanceof ImageViewTouch) { 34 | return ((ImageViewTouch) v).canScroll(dx) || super.canScroll(v, checkV, dx, x, y); 35 | } 36 | return super.canScroll(v, checkV, dx, x, y); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_empty_list_tip.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 29 | 30 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_connected.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/connect/protocol/IpMessageProtocol.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.connect.protocol 2 | 3 | import java.util.* 4 | 5 | class IpMessageProtocol { 6 | 7 | var version: String = "1" //Version number 8 | var packetNum: String //Packet number 9 | var senderName: String = "Unknown" //Sender name 10 | var cmd: Int = 0 //Command 11 | var additionalSection: String = "" 12 | 13 | constructor() { 14 | packetNum = seconds 15 | } 16 | 17 | // 根据协议字符串初始化 18 | constructor(protocolString: String) { 19 | val args = protocolString.split(DELIMITER).toTypedArray() 20 | version = args[0] 21 | packetNum = args[1] 22 | senderName = args[2] 23 | cmd = args[3].toInt() 24 | additionalSection = if (args.size >= 5) args[4] else "" 25 | } 26 | 27 | constructor(senderName: String, cmd: Int, additionalSection: String = "") { 28 | packetNum = seconds 29 | this.senderName = senderName 30 | this.cmd = cmd 31 | this.additionalSection = additionalSection 32 | } 33 | 34 | //得到协议串 35 | val protocolString: String 36 | get() = version + DELIMITER + 37 | packetNum + DELIMITER + 38 | senderName + DELIMITER + 39 | cmd + DELIMITER + 40 | additionalSection 41 | 42 | //得到数据包编号,毫秒数 43 | private val seconds: String 44 | get() { 45 | return Date().time.toString() 46 | } 47 | 48 | companion object { 49 | const val DELIMITER = "-->" 50 | } 51 | } -------------------------------------------------------------------------------- /matisse/src/main/res/layout/photo_capture_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 35 | 36 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/HeartbeatCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import com.absinthe.kage.connect.protocol.IpMessageConst 4 | import com.absinthe.kage.connect.protocol.IpMessageProtocol 5 | import com.absinthe.kage.device.Command 6 | import com.absinthe.kage.device.CommandBuilder 7 | import com.absinthe.kage.device.client.Client 8 | import java.io.IOException 9 | 10 | class HeartbeatCommand : Command() { 11 | 12 | override fun pack(): String { 13 | return CommandBuilder() 14 | .with(this) 15 | .append(HEARTBEAT_MESSAGE) 16 | .build() 17 | } 18 | 19 | override fun doWork(client: Client, received: String) { 20 | try { 21 | client.writeToStream(pack()) 22 | } catch (e: IOException) { 23 | e.printStackTrace() 24 | client.offline() 25 | } 26 | } 27 | 28 | override fun parseReceived(received: String): Boolean { 29 | val splits = received.split(IpMessageProtocol.DELIMITER).toTypedArray() 30 | 31 | return if (splits.size == LENGTH) { 32 | try { 33 | splits[0].toInt() == cmd 34 | } catch (e: NumberFormatException) { 35 | e.printStackTrace() 36 | false 37 | } 38 | } else { 39 | false 40 | } 41 | } 42 | 43 | companion object { 44 | const val HEARTBEAT_MESSAGE = "HEARTBEAT" 45 | const val LENGTH = 2 46 | } 47 | 48 | init { 49 | cmd = IpMessageConst.HEARTBEAT 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/absinthe/kage/device/cmd/MediaPausePlayingCommand.kt: -------------------------------------------------------------------------------- 1 | package com.absinthe.kage.device.cmd 2 | 3 | import android.media.session.PlaybackState 4 | import com.absinthe.kage.connect.protocol.IpMessageConst 5 | import com.absinthe.kage.connect.proxy.* 6 | import com.absinthe.kage.device.Command 7 | import com.absinthe.kage.device.CommandBuilder 8 | import com.absinthe.kage.device.client.Client 9 | import com.absinthe.kage.media.audio.AudioPlayer 10 | import com.absinthe.kage.media.video.LocalVideoPlayback 11 | import timber.log.Timber 12 | 13 | class MediaPausePlayingCommand : Command() { 14 | 15 | override fun pack(): String { 16 | return CommandBuilder() 17 | .with(this) 18 | .append(PAUSE_MESSAGE) 19 | .build() 20 | } 21 | 22 | override fun doWork(client: Client, received: String) { 23 | if (BaseProxy.CURRENT_MODE == MODE_AUDIO) { 24 | Timber.d("PlaybackState.STATE_PLAYING") 25 | if (AudioPlayer.playState == PlaybackState.STATE_PLAYING) { 26 | AudioPlayer.pause() 27 | } 28 | } else if (BaseProxy.CURRENT_MODE == MODE_VIDEO) { 29 | LocalVideoPlayback.INSTANCE?.let { 30 | if (it.state == PlaybackState.STATE_PAUSED) 31 | it.play() 32 | } 33 | } 34 | } 35 | 36 | override fun parseReceived(received: String): Boolean { 37 | return false 38 | } 39 | 40 | companion object { 41 | const val PAUSE_MESSAGE = "PAUSE" 42 | } 43 | 44 | init { 45 | cmd = IpMessageConst.MEDIA_PAUSE 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 28 | 29 |