Commit 45f8f582 authored by Jeremy JAMET's avatar Jeremy JAMET

Merge branch 'release/1.2'

parents dab551d1 bc4eb35b
......@@ -10,10 +10,10 @@ Yes, Keyboard Switcher is under **free license (GPL v3)** and **without advertis
## Contributions
You can contribute in different ways to help us on our work.
You can contribute in different ways to help me on my work.
* Add features by a **[merge request](https://docs.gitlab.com/ee/gitlab-basics/add-merge-request.html)**.
* **[Donate](https://www.kunzisoft.com/donation)** ♥‿♥ to support us.
* **[Donate](https://www.kunzisoft.com/donation)** ♥‿♥ to support me.
## Download
......@@ -29,7 +29,7 @@ You can contribute in different ways to help us on our work.
## License
Copyright (c) 2018 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of Keyboard Switcher.
......
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
compileSdkVersion 28
defaultConfig {
applicationId "com.kunzisoft.keyboard.switcher"
minSdkVersion 14
targetSdkVersion 27
versionCode 2
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
targetSdkVersion 28
versionCode 3
versionName "1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
......@@ -18,12 +18,11 @@ android {
}
}
def supportVersion = "27.1.1"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.0'
implementation 'com.google.android.material:material:1.0.0'
implementation "androidx.appcompat:appcompat:1.0.2"
implementation "androidx.preference:preference:1.0.0"
implementation "androidx.legacy:legacy-preference-v14:1.0.0"
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.1'
}
......@@ -22,7 +22,11 @@
</intent-filter>
</activity>
<activity android:name=".KeyboardManagerActivity"
android:label=""/>
android:label="">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity android:name=".boot.BootUpActivity"
android:label="" />
<receiver android:enabled="true" android:name=".boot.BootUpReceiver"
......@@ -35,7 +39,6 @@
</receiver>
<service android:name=".OverlayShowingService" />
<service android:name=".KeyboardNotificationService" />
<receiver android:name=".KeyboardWidgetProvider" >
<intent-filter>
......
package com.kunzisoft.keyboard.switcher;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
/**
* Activity to show keyboard manager
*/
public class KeyboardManagerActivity extends AppCompatActivity {
public static final String DELAY_SHOW_KEY = "DELAY_SHOW_KEY";
private long delay = 200L;
private InputMethodManager imeManager;
private View rootView;
enum DialogState {
PICKING, CHOSEN
NONE, PICKING, CHOSEN
}
private DialogState mState;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mState = DialogState.NONE;
setContentView(R.layout.empty);
rootView = findViewById(R.id.root_view);
super.onCreate(savedInstanceState);
imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (getIntent() != null) {
delay = getIntent().getLongExtra(DELAY_SHOW_KEY, delay);
}
}
@Override
......@@ -44,13 +56,12 @@ public class KeyboardManagerActivity extends AppCompatActivity {
rootView.postDelayed(new Runnable() {
@Override
public void run() {
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imeManager != null) {
imeManager.showInputMethodPicker();
}
mState = DialogState.PICKING;
}
}, 100);
}, delay);
}
@Override
......
package com.kunzisoft.keyboard.switcher;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import com.kunzisoft.keyboard.switcher.utils.Utilities;
import static android.content.ContentValues.TAG;
public class KeyboardNotificationService extends Service {
private static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.channel";
private static final String CHANNEL_NAME_KEYBOARD = "Keyboard switcher notification";
private NotificationManager notificationManager;
private Thread cleanNotificationTimer;
private int notificationId = 1;
private long notificationTimeoutMilliSecs;
public KeyboardNotificationService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Create notification channel for Oreo+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_KEYBOARD,
CHANNEL_NAME_KEYBOARD,
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Get settings
// TODO Get timeout
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
notificationTimeoutMilliSecs = 100000;
if (intent == null) {
Log.w(TAG, "null intent");
} else {
newNotification();
}
return START_NOT_STICKY;
}
private void newNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
.setSmallIcon(R.drawable.ic_notification_white_24dp)
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
.setContentTitle(getString(R.string.notification_title))
.setAutoCancel(false)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
.setContentText(getString(R.string.notification_content_text))
.setContentIntent(Utilities.getPendingIntent(this));
notificationManager.cancel(notificationId);
notificationManager.notify(notificationId, builder.build());
/*
stopTask(cleanNotificationTimer);
cleanNotificationTimer = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(notificationTimeoutMilliSecs);
} catch (InterruptedException e) {
cleanNotificationTimer = null;
return;
}
notificationManager.cancel(notificationId);
}
});
cleanNotificationTimer.start();
*/
}
private void stopTask(Thread task) {
if (task != null && task.isAlive())
task.interrupt();
}
@Override
public void onDestroy() {
super.onDestroy();
notificationManager.cancel(notificationId);
}
}
package com.kunzisoft.keyboard.switcher;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import com.kunzisoft.keyboard.switcher.utils.Utilities;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
public class NotificationBuilder {
public static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.channel";
public static final String CHANNEL_NAME_KEYBOARD = "Keyboard switcher notification";
private NotificationManager mNotificationManager;
private int notificationId = 45;
public NotificationBuilder(NotificationManager notificationManager) {
this.mNotificationManager = notificationManager;
// Create notification channel for Oreo+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_KEYBOARD,
CHANNEL_NAME_KEYBOARD,
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
}
}
public void createKeyboardNotification(Context context) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID_KEYBOARD)
.setSmallIcon(R.drawable.ic_notification_white_24dp)
.setColor(ContextCompat.getColor(context, R.color.colorPrimary))
.setContentTitle(context.getString(R.string.notification_keyboard_title))
.setAutoCancel(false)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
.setContentText(context.getString(R.string.notification_keyboard_content_text))
.setContentIntent(Utilities.getPendingIntent(context, 500L)); // Trick 500ms delay to show th dialog
mNotificationManager.cancel(notificationId);
mNotificationManager.notify(notificationId, builder.build());
}
public void cancelKeyboardNotification() {
mNotificationManager.cancel(notificationId);
}
}
......@@ -9,8 +9,6 @@ import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.ColorRes;
import android.support.v4.content.ContextCompat;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
......@@ -22,7 +20,12 @@ import android.widget.ImageView;
import com.kunzisoft.keyboard.switcher.utils.Utilities;
import androidx.annotation.ColorRes;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static com.kunzisoft.keyboard.switcher.NotificationBuilder.CHANNEL_ID_KEYBOARD;
public class OverlayShowingService extends Service implements OnTouchListener, OnClickListener {
......@@ -38,7 +41,9 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
private int originalXPos;
private int originalYPos;
private boolean moving;
private WindowManager wm;
private WindowManager windowManager;
private boolean lockedButton;
@Override
public IBinder onBind(Intent intent) {
......@@ -50,14 +55,28 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// To keep the service on top
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
.setSmallIcon(R.drawable.ic_notification_button_white_24dp)
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
.setContentTitle(getString(R.string.notification_floating_button_title))
.setContentText(getString(R.string.notification_floating_button_content_text))
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_SECRET);
startForeground(56, builder.build());
}
preferences = PreferenceManager.getDefaultSharedPreferences(this);
if (preferences.getBoolean(getString(R.string.settings_floating_button_key), false)) {
// check Button Position
boolean isAtRight = preferences.getBoolean(getString(R.string.settings_position_button_key), true);
boolean isAtRight = preferences.getBoolean(getString(R.string.settings_floating_button_position_key), true);
lockedButton = preferences.getBoolean(getString(R.string.settings_floating_button_lock_key), false);
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
overlayedButton = new ImageView(this);
@ColorRes int color = preferences.getInt(getString(R.string.settings_colors_key), ContextCompat.getColor(this, R.color.colorPrimary));
......@@ -94,7 +113,7 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
yPositionToSave = preferences.getInt(Y_POSITION_PREFERENCE_KEY, 0);
params.y = yPositionToSave;
}
wm.addView(overlayedButton, params);
windowManager.addView(overlayedButton, params);
topLeftView = new View(this);
LayoutParams topLeftParams =
......@@ -112,13 +131,14 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
topLeftParams.y = 0;
topLeftParams.width = 0;
topLeftParams.height = 0;
wm.addView(topLeftView, topLeftParams);
windowManager.addView(topLeftView, topLeftParams);
}
}
private void getPositionOnScreen() {
int[] location = new int[2];
overlayedButton.getLocationOnScreen(location);
if (overlayedButton != null)
overlayedButton.getLocationOnScreen(location);
originalXPos = location[0];
originalYPos = location[1];
......@@ -132,11 +152,19 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
public boolean onTouch(View view, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
float y = event.getRawY();
// Consume the touch and click if the button is locked
if (lockedButton) {
if (event.getAction() == MotionEvent.ACTION_UP) {
view.playSoundEffect(android.view.SoundEffectConstants.CLICK);
onClick(view);
}
return true;
}
float y = event.getRawY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
moving = false;
getPositionOnScreen();
......@@ -148,8 +176,6 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
int[] topLeftLocationOnScreen = new int[2];
topLeftView.getLocationOnScreen(topLeftLocationOnScreen);
float y = event.getRawY();
WindowManager.LayoutParams params = (LayoutParams) overlayedButton.getLayoutParams();
int newX = (int) (offsetX);
......@@ -163,7 +189,7 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
params.y = newY - (topLeftLocationOnScreen[1]);
yPositionToSave = params.y;
wm.updateViewLayout(overlayedButton, params);
windowManager.updateViewLayout(overlayedButton, params);
moving = true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
saveYPreferencePosition();
......@@ -191,8 +217,8 @@ public class OverlayShowingService extends Service implements OnTouchListener, O
if (overlayedButton != null) {
saveYPreferencePosition();
wm.removeView(overlayedButton);
wm.removeView(topLeftView);
windowManager.removeView(overlayedButton);
windowManager.removeView(topLeftView);
overlayedButton = null;
topLeftView = null;
}
......
package com.kunzisoft.keyboard.switcher.boot;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import com.kunzisoft.keyboard.switcher.KeyboardNotificationService;
import com.kunzisoft.keyboard.switcher.NotificationBuilder;
import com.kunzisoft.keyboard.switcher.OverlayShowingService;
import com.kunzisoft.keyboard.switcher.R;
import androidx.appcompat.app.AppCompatActivity;
/**
* Utility class to show keyboard button at startup
*/
public class BootUpActivity extends AppCompatActivity{
private Intent floatingButtonService;
private void stopFloatingButtonService() {
stopService(new Intent(this, OverlayShowingService.class));
}
private void startFloatingButtonService() {
startService(new Intent(this, OverlayShowingService.class));
}
@Override
public void onCreate(Bundle savedInstanceState) {
......@@ -27,29 +34,22 @@ public class BootUpActivity extends AppCompatActivity{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
if (preferences.getBoolean(getString(R.string.settings_notification_key), false)) {
Intent notificationService = new Intent(this, KeyboardNotificationService.class);
startService(notificationService);
NotificationBuilder notificationBuilder =
new NotificationBuilder((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
notificationBuilder.createKeyboardNotification(this);
}
stopFloatingButtonService();
if (preferences.getBoolean(getString(R.string.settings_floating_button_key), false)) {
floatingButtonService = new Intent(this, OverlayShowingService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkDrawOverlayPermission();
if (Settings.canDrawOverlays(getApplicationContext())) {
startFloatingButtonService();
}
} else {
startService(floatingButtonService);
finish();
startFloatingButtonService();
}
} else {
finish();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void checkDrawOverlayPermission() {
/* Do nothing here if not permitted */
if (Settings.canDrawOverlays(getApplicationContext())) {
startService(floatingButtonService);
}
finish();
}
}
......@@ -4,10 +4,10 @@ import android.app.Dialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import android.text.SpannableStringBuilder;
import com.kunzisoft.keyboard.switcher.R;
......
......@@ -6,14 +6,14 @@ import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.Html;
import android.text.SpannableStringBuilder;
import com.kunzisoft.keyboard.switcher.R;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
/**
* Custom Dialog that asks the user to download the pro version or make a donation.
*/
......@@ -45,10 +45,13 @@ public class WarningFloatingButtonDialog extends DialogFragment {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
stringBuilder.append(Html.fromHtml(getString(R.string.floating_button_warning)));
stringBuilder.append(getString(R.string.floating_button_warning));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stringBuilder.append("\n\n").append(getString(R.string.floating_button_notification_warning));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !Settings.canDrawOverlays(getActivity())) {
stringBuilder.append("\n\n").append(Html.fromHtml(getString(R.string.floating_button_above_screen)));
stringBuilder.append("\n\n").append(getString(R.string.floating_button_above_screen));
}
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
......
......@@ -5,9 +5,6 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
......@@ -16,8 +13,13 @@ import com.kunzisoft.keyboard.switcher.boot.BootUpActivity;
import com.kunzisoft.keyboard.switcher.dialogs.AppDialog;
import com.kunzisoft.keyboard.switcher.dialogs.WarningFloatingButtonDialog;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
public class PreferenceActivity extends AppCompatActivity implements WarningFloatingButtonDialog.OnFloatingButtonListener{
private static final String TAG_PREFERENCE_FRAGMENT = "TAG_PREFERENCE_FRAGMENT";
private PreferenceFragment preferenceFragment;
@RequiresApi(api = Build.VERSION_CODES.M)
......@@ -26,14 +28,18 @@ public class PreferenceActivity extends AppCompatActivity implements WarningFloa
super.onCreate(savedInstanceState);
setContentView(R.layout.preference_activity);
// Manage fragment who contains list of preferences
preferenceFragment =
(PreferenceFragment) getSupportFragmentManager().findFragmentByTag(TAG_PREFERENCE_FRAGMENT);
preferenceFragment = new PreferenceFragment();
if(preferenceFragment == null)
preferenceFragment = new PreferenceFragment();
Intent bootUpIntent = new Intent(this, BootUpActivity.class);
startActivity(bootUpIntent);
startActivity(new Intent(this, BootUpActivity.class));
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, preferenceFragment)
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, preferenceFragment, TAG_PREFERENCE_FRAGMENT)
.commit();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
......@@ -68,17 +74,22 @@ public class PreferenceActivity extends AppCompatActivity implements WarningFloa
@Override
public void onFloatingButtonDialogPositiveButtonClick() {
if (preferenceFragment != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
preferenceFragment.checkDrawOverlayPermission();
} else {
preferenceFragment.startFloatingButtonService();
}
}
if (preferenceFragment != null)
preferenceFragment.startFloatingButtonAndCheckButton();
}
@Override
public void onFloatingButtonDialogNegativeButtonClick() {
preferenceFragment.stopFloatingButtonService();
if (preferenceFragment != null)
preferenceFragment.stopFloatingButtonAndUncheckedButton();
}
@Override
protected void onStop() {
super.onStop();
// To avoid flickering and open time
if (preferenceFragment != null && !preferenceFragment.isTryingToOpenExternalDialog())
finish();
}
}
package com.kunzisoft.keyboard.switcher.utils;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.view.inputmethod.InputMethodManager;
import com.kunzisoft.keyboard.switcher.KeyboardManagerActivity;
import com.kunzisoft.keyboard.switcher.R;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
......@@ -15,9 +20,18 @@ public class Utilities {
public static void openAvailableKeyboards(@Nullable Context context) {
if (context != null) {
Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
try {
Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);