Skip to content

Commit

Permalink
commit files
Browse files Browse the repository at this point in the history
  • Loading branch information
liaohuqiu committed Oct 20, 2015
0 parents commit a7f2a9d
Show file tree
Hide file tree
Showing 36 changed files with 987 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .gitignore
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
20 changes: 20 additions & 0 deletions README.md
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
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
27 changes: 27 additions & 0 deletions app/build.gradle
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'
}
17 changes: 17 additions & 0 deletions app/proguard-rules.pro
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 *;
#}
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);
}
}
43 changes: 43 additions & 0 deletions app/src/main/AndroidManifest.xml
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>
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);
}
}
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();
}
}
}
}
Loading

0 comments on commit a7f2a9d

Please sign in to comment.