Skip to content

Commit

Permalink
feat: settings screen
Browse files Browse the repository at this point in the history
1. Widget switch
2. Vibration switch
3. Widget color picker
4. Dropped 'Close' button
5. Improved animation of widget expansion
  • Loading branch information
yauhen-l committed Sep 17, 2020
1 parent c400ac0 commit 97e86c7
Show file tree
Hide file tree
Showing 16 changed files with 316 additions and 106 deletions.
10 changes: 6 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ android {
applicationId "by.yauhenl.gardine"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "0.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 2
versionName "0.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
Expand All @@ -23,7 +23,9 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation "androidx.preference:preference:1.0.0"
implementation "com.github.skydoves:colorpickerpreference:2.0.4"
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.16.1'
}
Expand Down
23 changes: 12 additions & 11 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="by.yauhenl.gardine">
package="by.yauhenl.gardine" >

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="by.yauhenl.gardine.MainActivity">
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:label="@string/accessibility_service_name"
android:name="by.yauhenl.gardine.GardineWidgetService"
android:name=".GardineWidgetService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
android:label="@string/accessibility_service_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>

<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibilityservice"/>
android:resource="@xml/accessibilityservice" />
</service>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package by.yauhenl.gardine;

import android.view.View;
import android.view.ViewTreeObserver;

import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;

public class AntipodeViewsLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {

private View a, b;
private int av, bv;

public AntipodeViewsLayoutListener(View a, View b) {
this.a = a;
this.b = b;
this.setVisibility();
}

@Override
public void onGlobalLayout() {
boolean aVisibilityChanged = a.getVisibility() != av;
boolean bVisibilityChanged = b.getVisibility() != bv;

this.setVisibility();

if (aVisibilityChanged) {
this.inverse(a, b);
}

if (bVisibilityChanged) {
this.inverse(b, a);
}

// At least one view has to be visible
if(av == GONE && bv == GONE) {
this.a.setVisibility(VISIBLE);
}
}

private void setVisibility() {
this.av = this.a.getVisibility();
this.bv = this.b.getVisibility();
}

private void inverse(View v1, View v2) {
switch (v1.getVisibility()) {
case GONE:
v2.setVisibility(VISIBLE);
break;
case VISIBLE:
v2.setVisibility(GONE);
break;
case INVISIBLE:
break;
}
}
}
115 changes: 77 additions & 38 deletions app/src/main/java/by/yauhenl/gardine/GardineWidgetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
Expand All @@ -18,34 +19,33 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;

import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;

import java.util.ArrayDeque;
import java.util.ArrayList;

import static android.widget.AdapterView.INVALID_POSITION;

public class GardineWidgetService extends AccessibilityService {

public static final String LOG_TAG_COORD = "coord";
public static final String LOG_TAG_RECENT_APPS = "recent_apps";
public static final String LOG_TAG_EVENT = "event";

// TODO: move to preferences
private static final int MAX_ITEMS = 6;
private static final int SHOW_X_THRESHOLD = 30;
private static final int SCROLL_Y_THRESHOLD = 45;
private static final int VIBRATE_DURATION = 20;
private static final int MAX_Y_DIFF = MAX_ITEMS * SCROLL_Y_THRESHOLD - SCROLL_Y_THRESHOLD/2;
private static final int MAX_Y_DIFF = MAX_ITEMS * SCROLL_Y_THRESHOLD - SCROLL_Y_THRESHOLD / 2;
private static final int MIN_Y_DIFF = 0;

private WindowManager windowManager;
private View gardine;
private View widget;
private DiscardingStack<App> recentActivities;
private String currentAppPackage;
private ArrayAdapter<App> recentAppsAdapter;
private Vibrator vibrator;
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;

public GardineWidgetService() {
this.recentActivities = new DiscardingStack<>(MAX_ITEMS);
Expand All @@ -68,11 +68,10 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
return;
}
Log.d(LOG_TAG_EVENT, "Window in foreground: " + event.getPackageName());
Log.d(LoggingUtils.EVENT_TAG, "Window in foreground: " + event.getPackageName());
if (event.getPackageName() == null || event.getClassName() == null) {
return;
}
this.currentAppPackage = event.getPackageName().toString();

ComponentName componentName = new ComponentName(
event.getPackageName().toString(),
Expand All @@ -84,20 +83,23 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
try {
activityInfo = pm.getActivityInfo(componentName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.i(LOG_TAG_RECENT_APPS, "Ignore window of component: " + componentName);
Log.i(LoggingUtils.RECENT_APPS_TAG, "Ignore window of component: " + componentName);
return;
}

this.currentAppPackage = event.getPackageName().toString();

Intent startIntent = pm.getLaunchIntentForPackage(activityInfo.packageName);
if (startIntent == null) {
Log.d(LOG_TAG_RECENT_APPS, "Skipping package " + activityInfo.packageName + " due to absence of launch intent");
Log.d(LoggingUtils.RECENT_APPS_TAG, "Skipping package " + activityInfo.packageName + " due to absence of launch intent");
return;
}

String label = pm.getApplicationLabel(activityInfo.applicationInfo).toString();
App a = new App(label, activityInfo.packageName, startIntent);
this.recentActivities.add(a);

Log.i(LOG_TAG_RECENT_APPS, "Added app to the stack: " + a.toLogString());
Log.i(LoggingUtils.RECENT_APPS_TAG, "Added app to the stack: " + a.toLogString());
}


Expand All @@ -106,13 +108,29 @@ public void onInterrupt() {

}

private void actualize_widget_visibility(SharedPreferences sharedPreferences) {
boolean isWidgetVisible = sharedPreferences.getBoolean(getString(R.string.pref_widget_enabled_key), true);
if (isWidgetVisible) {
widget.setVisibility(View.VISIBLE);
} else {
widget.setVisibility(View.GONE);
}
}

private void actualize_widget_background(SharedPreferences prefs, View view) {
final int defaultColor = ContextCompat.getColor(getApplicationContext(), R.color.hidden);
int backgroundColor = prefs.getInt(getString(R.string.pref_widget_background_color_key), defaultColor);
view.setBackgroundColor(backgroundColor);
}


@Override
public void onCreate() {
super.onCreate();

this.vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

gardine = LayoutInflater.from(this).inflate(R.layout.gardine, null);
widget = LayoutInflater.from(this).inflate(R.layout.widget, null);

int LAYOUT_FLAG;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Expand All @@ -133,27 +151,41 @@ public void onCreate() {
params.y = 0;

windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
windowManager.addView(gardine, params);
windowManager.addView(widget, params);


final View collapsedView = gardine.findViewById(R.id.collapse_view);
final View expandedView = gardine.findViewById(R.id.gardine);
final View rootContainer = gardine.findViewById(R.id.root_container);
final View collapsedView = widget.findViewById(R.id.collapse_view);
final View expandedView = widget.findViewById(R.id.gardine);
final View rootContainer = widget.findViewById(R.id.root_container);

widget.getViewTreeObserver().addOnGlobalLayoutListener(new AntipodeViewsLayoutListener(collapsedView, expandedView));

SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
actualize_widget_visibility(sharedPreferences);
actualize_widget_background(sharedPreferences, collapsedView);

this.preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences changedPreferences, String key) {
if (getString(R.string.pref_widget_enabled_key).equals(key)) {
actualize_widget_visibility(changedPreferences);
return;
}
if (getString(R.string.pref_widget_background_color_key).equals(key)) {
actualize_widget_background(changedPreferences, collapsedView);
return;
}
}
};
sharedPreferences.registerOnSharedPreferenceChangeListener(this.preferenceChangeListener);

this.recentAppsAdapter = new ArrayAdapter<>(
this, R.layout.item, R.id.item,
new ArrayList<App>(MAX_ITEMS));

final ListView tasksList = (ListView) gardine.findViewById(R.id.tasks_list);
final ListView tasksList = (ListView) widget.findViewById(R.id.tasks_list);
tasksList.setAdapter(this.recentAppsAdapter);

ImageView closeButtonCollapsed = (ImageView) gardine.findViewById(R.id.destroy_btn);
closeButtonCollapsed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopSelf();
}
});

rootContainer.setOnTouchListener(new View.OnTouchListener() {
private float initialTouchX;
private float initialShowY;
Expand All @@ -165,23 +197,23 @@ private boolean isHidden() {

private void hide() {
expandedView.setVisibility(View.GONE);
collapsedView.setVisibility(View.VISIBLE);
//collapsedView.setVisibility(View.VISIBLE);
}

private void show() {
collapsedView.setVisibility(View.GONE);
expandedView.setVisibility(View.VISIBLE);
//expandedView.setVisibility(View.VISIBLE);
}

@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(LOG_TAG_COORD, "DOWN at " + event.getRawX() + ", " + event.getRawY());
Log.d(LoggingUtils.COORD_TAG, "DOWN at " + event.getRawX() + ", " + event.getRawY());
initialTouchX = event.getRawX();
return true;
case MotionEvent.ACTION_UP:
Log.d(LOG_TAG_COORD, "UP at " + event.getRawX() + ", " + event.getRawY());
Log.d(LoggingUtils.COORD_TAG, "UP at " + event.getRawX() + ", " + event.getRawY());


if (!isHidden()) {
Expand All @@ -200,7 +232,7 @@ public boolean onTouch(View v, MotionEvent event) {

if (isHidden()) {
if (initialTouchX - event.getRawX() > SHOW_X_THRESHOLD) {
GardineWidgetService.this.actualizeRecentApps();
GardineWidgetService.this.actualize_recent_apps();

this.initialShowX = event.getRawX();
this.initialShowY = event.getRawY();
Expand All @@ -224,16 +256,16 @@ public boolean onTouch(View v, MotionEvent event) {

if (itemsNumber > 0) {
int selectedItem = (yd / SCROLL_Y_THRESHOLD);
if(selectedItem < 0) {
if (selectedItem < 0) {
selectedItem = 0;
} else if (selectedItem >= itemsNumber) {
selectedItem = itemsNumber - 1;
}

int prevItem = tasksList.getCheckedItemPosition();
if (prevItem != selectedItem) {
Log.d(LOG_TAG_COORD, "Vibrate at Ydiff=" + yd + ", prevItem=" + prevItem + ", curItem=" + selectedItem);
vibrator.vibrate(VIBRATE_DURATION);
Log.d(LoggingUtils.COORD_TAG, "Item changed at Ydiff=" + yd + ", prevItem=" + prevItem + ", curItem=" + selectedItem);
vibrate_on_scroll();
tasksList.setItemChecked(selectedItem, true);
}
}
Expand All @@ -247,19 +279,26 @@ public boolean onTouch(View v, MotionEvent event) {
});
}

private void vibrate_on_scroll() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
if (prefs.getBoolean(getString(R.string.pref_vibrate_on_scroll_key), true) && this.vibrator != null) {
this.vibrator.vibrate(VIBRATE_DURATION);
}
}

@Override
public void onDestroy() {
super.onDestroy();
if (gardine != null) windowManager.removeView(gardine);
if (widget != null) windowManager.removeView(widget);
}

private void actualizeRecentApps() {
private void actualize_recent_apps() {
this.recentAppsAdapter.clear();

ArrayDeque<App> recentApps = this.recentActivities.getAll();
if(this.currentAppPackage != null) {
if (this.currentAppPackage != null) {
boolean removed = recentApps.removeFirstOccurrence(new App(null, this.currentAppPackage, null));
Log.d(LOG_TAG_RECENT_APPS, "Current app " + this.currentAppPackage + " has been removed: " + removed);
Log.d(LoggingUtils.RECENT_APPS_TAG, "Current app " + this.currentAppPackage + " has been removed: " + removed);
}
this.recentAppsAdapter.addAll(recentApps);
this.recentAppsAdapter.notifyDataSetChanged();
Expand Down
Loading

0 comments on commit 97e86c7

Please sign in to comment.