-
Notifications
You must be signed in to change notification settings - Fork 269
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a7f2a9d
Showing
36 changed files
with
987 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# built application files | ||
*.ap_ | ||
|
||
# files for the dex VM | ||
*.dex | ||
*.iml | ||
|
||
# Java class files | ||
*.class | ||
|
||
# generated files | ||
bin/ | ||
gen/ | ||
*target/ | ||
*build/ | ||
.gradle/ | ||
.idea/ | ||
gen-external-apklibs/ | ||
|
||
# Local configuration file (sdk path, etc) | ||
local.properties | ||
|
||
# Eclipse project files | ||
.classpath | ||
.project | ||
|
||
# Mac os | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
UC 浏览器复制,无需权限提示悬浮窗实现 | ||
|
||
[Android无需权限显示悬浮窗, 兼谈逆向分析app](http://www.jianshu.com/p/167fd5f47d5c) 文中提到,`type` 为 `WindowManager.LayoutParams.TYPE_TOAST` 的 `WindowManager.LayoutParam` 无需权限,即可让 View 显示。 | ||
|
||
本项目模拟实现该功能,即:开机自动启动的 Service 监听剪切板。复制之后,在屏幕顶部显示一个悬浮窗,显示剪贴板内容。点击悬浮窗,跳转到 Activity 页面显示。 | ||
|
||
包含以下几个小功能点: | ||
|
||
1. 监控剪切板 | ||
2. WindowManager 的使用 | ||
3. Service 的使用 | ||
4. 悬浮窗处理: | ||
|
||
1. 黑色半透明背景 | ||
2. 触摸背景关闭 | ||
3. 点击内容跳转 | ||
4. 处理返回键关闭 | ||
|
||
5. 开机自动启动 Service | ||
6. WakeLock 启动 Service |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
apply plugin: 'com.android.application' | ||
|
||
android { | ||
compileSdkVersion 23 | ||
buildToolsVersion "23.0.1" | ||
|
||
defaultConfig { | ||
applicationId "in.srain.cube.demos.uctoast" | ||
minSdkVersion 11 | ||
targetSdkVersion 23 | ||
versionCode 1 | ||
versionName "1.0" | ||
} | ||
buildTypes { | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
compile fileTree(dir: 'libs', include: ['*.jar']) | ||
testCompile 'junit:junit:4.12' | ||
compile 'com.android.support:appcompat-v7:23.0.1' | ||
compile 'com.android.support:design:23.0.1' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Add project specific ProGuard rules here. | ||
# By default, the flags in this file are appended to flags specified | ||
# in /Users/srain/apps/adt/sdk/tools/proguard/proguard-android.txt | ||
# You can edit the include path and order by changing the proguardFiles | ||
# directive in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# Add any project specific keep options here: | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} |
13 changes: 13 additions & 0 deletions
13
app/src/androidTest/java/in/srain/cube/demos/uctoast/uctoast/ApplicationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package in.srain.cube.demos.uctoast.uctoast; | ||
|
||
import android.app.Application; | ||
import android.test.ApplicationTestCase; | ||
|
||
/** | ||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> | ||
*/ | ||
public class ApplicationTest extends ApplicationTestCase<Application> { | ||
public ApplicationTest() { | ||
super(Application.class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest | ||
package="in.srain.cube.demos.uctoast" | ||
xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||
<uses-permission android:name="android.permission.WAKE_LOCK" /> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:supportsRtl="true" | ||
android:theme="@style/AppTheme"> | ||
<activity | ||
android:name=".MainActivity" | ||
android:label="@string/app_name" | ||
android:launchMode="singleInstance" | ||
android:theme="@style/AppTheme.NoActionBar"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
|
||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
|
||
<service | ||
android:name=".ListenClipboardService" | ||
android:enabled="true" | ||
android:exported="true" | ||
android:process=":process"></service> | ||
|
||
<receiver | ||
android:name=".BootCompletedReceiver" | ||
android:enabled="true" | ||
android:exported="false"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.BOOT_COMPLETED" /> | ||
</intent-filter> | ||
</receiver> | ||
</application> | ||
|
||
</manifest> |
14 changes: 14 additions & 0 deletions
14
app/src/main/java/in/srain/cube/demos/uctoast/BootCompletedReceiver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package in.srain.cube.demos.uctoast; | ||
|
||
|
||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.support.v4.content.WakefulBroadcastReceiver; | ||
|
||
public class BootCompletedReceiver extends WakefulBroadcastReceiver { | ||
|
||
@Override | ||
public void onReceive(Context context, Intent intent) { | ||
ListenClipboardService.startForWeakLock(context, intent); | ||
} | ||
} |
205 changes: 205 additions & 0 deletions
205
app/src/main/java/in/srain/cube/demos/uctoast/ListenClipboardService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
package in.srain.cube.demos.uctoast; | ||
|
||
import android.app.Service; | ||
import android.content.*; | ||
import android.graphics.PixelFormat; | ||
import android.graphics.Rect; | ||
import android.os.IBinder; | ||
import android.support.v4.content.WakefulBroadcastReceiver; | ||
import android.text.TextUtils; | ||
import android.view.*; | ||
import android.widget.TextView; | ||
|
||
public final class ListenClipboardService extends Service { | ||
|
||
private static final String KEY_FOR_WEAK_LOCK = "weak-lock"; | ||
private static final String KEY_FOR_CMD = "cmd"; | ||
private static final String KEY_FOR_CONTENT = "content"; | ||
|
||
private static final String CMD_TEST = "test"; | ||
|
||
private static CharSequence sLastContent = null; | ||
|
||
private ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener = new ClipboardManager.OnPrimaryClipChangedListener() { | ||
public void onPrimaryClipChanged() { | ||
performClipboardCheck(); | ||
} | ||
}; | ||
|
||
public static void start(Context context) { | ||
Intent serviceIntent = new Intent(context, ListenClipboardService.class); | ||
context.startService(serviceIntent); | ||
} | ||
|
||
/** | ||
* for dev | ||
* | ||
* @param context | ||
* @param content | ||
*/ | ||
public static void startForTest(Context context, String content) { | ||
|
||
Intent serviceIntent = new Intent(context, ListenClipboardService.class); | ||
serviceIntent.putExtra(KEY_FOR_CMD, CMD_TEST); | ||
serviceIntent.putExtra(KEY_FOR_CONTENT, content); | ||
context.startService(serviceIntent); | ||
} | ||
|
||
public static void startForWeakLock(Context context, Intent intent) { | ||
|
||
Intent serviceIntent = new Intent(context, ListenClipboardService.class); | ||
context.startService(serviceIntent); | ||
|
||
intent.putExtra(ListenClipboardService.KEY_FOR_WEAK_LOCK, true); | ||
Intent myIntent = new Intent(context, ListenClipboardService.class); | ||
|
||
// using wake lock to start service | ||
WakefulBroadcastReceiver.startWakefulService(context, myIntent); | ||
} | ||
|
||
@Override | ||
public void onCreate() { | ||
((ClipboardManager) getSystemService(CLIPBOARD_SERVICE)).addPrimaryClipChangedListener(mOnPrimaryClipChangedListener); | ||
} | ||
|
||
@Override | ||
public void onDestroy() { | ||
super.onDestroy(); | ||
((ClipboardManager) getSystemService(CLIPBOARD_SERVICE)).removePrimaryClipChangedListener(mOnPrimaryClipChangedListener); | ||
} | ||
|
||
@Override | ||
public int onStartCommand(Intent intent, int flags, int startId) { | ||
|
||
Utils.printIntent("onStartCommand", intent); | ||
|
||
if (intent != null) { | ||
// remove wake lock | ||
if (intent.getBooleanExtra(KEY_FOR_WEAK_LOCK, false)) { | ||
BootCompletedReceiver.completeWakefulIntent(intent); | ||
} | ||
String cmd = intent.getStringExtra(KEY_FOR_CMD); | ||
if (!TextUtils.isEmpty(cmd)) { | ||
if (cmd.equals(CMD_TEST)) { | ||
String content = intent.getStringExtra(KEY_FOR_CONTENT); | ||
showContent(content); | ||
} | ||
} | ||
} | ||
return START_STICKY; | ||
} | ||
|
||
@Override | ||
public IBinder onBind(Intent intent) { | ||
return null; | ||
} | ||
|
||
private void performClipboardCheck() { | ||
ClipboardManager cb = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); | ||
CharSequence content = cb.getText(); | ||
if (TextUtils.isEmpty(content)) { | ||
return; | ||
} | ||
showContent(content); | ||
} | ||
|
||
private void showContent(CharSequence content) { | ||
if (sLastContent != null && sLastContent.equals(content) || content == null) { | ||
return; | ||
} | ||
sLastContent = content; | ||
|
||
TipViewController controller = new TipViewController(getApplication(), content); | ||
controller.show(); | ||
} | ||
|
||
final static class TipViewController implements View.OnClickListener, View.OnTouchListener, ViewContainer.KeyEventHandler { | ||
|
||
private WindowManager mWindowManager; | ||
private Context mContext; | ||
private ViewContainer mWholeView; | ||
private View mContentView; | ||
|
||
private CharSequence mContent; | ||
|
||
TipViewController(Context application, CharSequence content) { | ||
mContext = application; | ||
mContent = content; | ||
mWindowManager = (WindowManager) application.getSystemService(Context.WINDOW_SERVICE); | ||
} | ||
|
||
private void show() { | ||
|
||
ViewContainer view = (ViewContainer) View.inflate(mContext, R.layout.pop_view, null); | ||
|
||
// display content | ||
TextView textView = (TextView) view.findViewById(R.id.pop_view_text); | ||
textView.setText(mContent); | ||
|
||
mWholeView = view; | ||
mContentView = view.findViewById(R.id.pop_view_content_view); | ||
|
||
// event listeners | ||
mContentView.setOnClickListener(this); | ||
mWholeView.setOnTouchListener(this); | ||
mWholeView.setKeyEventHandler(this); | ||
|
||
int w = WindowManager.LayoutParams.MATCH_PARENT; | ||
int h = WindowManager.LayoutParams.MATCH_PARENT; | ||
|
||
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(w, h, WindowManager.LayoutParams.TYPE_TOAST, 0, PixelFormat.TRANSLUCENT); | ||
layoutParams.gravity = Gravity.TOP; | ||
|
||
mWindowManager.addView(mWholeView, layoutParams); | ||
} | ||
|
||
@Override | ||
public void onClick(View v) { | ||
removePoppedViewAndClear(); | ||
MainActivity.startForContent(mContext, mContent.toString()); | ||
} | ||
|
||
private void removePoppedViewAndClear() { | ||
|
||
// remove view | ||
if (mWindowManager != null && mWholeView != null) { | ||
mWindowManager.removeView(mWholeView); | ||
} | ||
|
||
// clear content | ||
sLastContent = null; | ||
|
||
// remove listeners | ||
mContentView.setOnClickListener(null); | ||
mWholeView.setOnTouchListener(null); | ||
mWholeView.setKeyEventHandler(null); | ||
} | ||
|
||
/** | ||
* touch the outside of the content view, remove the popped view | ||
* | ||
* @param v | ||
* @param event | ||
* @return | ||
*/ | ||
@Override | ||
public boolean onTouch(View v, MotionEvent event) { | ||
int x = (int) event.getX(); | ||
int y = (int) event.getY(); | ||
Rect rect = new Rect(); | ||
mContentView.getGlobalVisibleRect(rect); | ||
if (!rect.contains(x, y)) { | ||
removePoppedViewAndClear(); | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public void onKeyEvent(KeyEvent event) { | ||
int keyCode = event.getKeyCode(); | ||
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { | ||
removePoppedViewAndClear(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.