Commit 364d2054 authored by Mudar Noufal's avatar Mudar Noufal

Initial commit

Build 10, v0.5-alpha
parent 89d503d4
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "ca.mudar.rotationquicksetting"
minSdkVersion 24
targetSdkVersion 25
versionCode 10
versionName "0.5-alpha"
resConfigs "en", "fr"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
ext.enableCrashlytics = true
}
debug {
applicationIdSuffix ".d"
versionNameSuffix "-build-" + getDate()
minifyEnabled false
ext.enableCrashlytics = false
}
debugCrashlytics.initWith(buildTypes.debug)
debugCrashlytics {
applicationIdSuffix ""
buildConfigField "boolean", "USE_CRASHLYTICS", "true"
ext.enableCrashlytics = true
}
}
}
def static getDate() {
new Date().format('ddMMMyyyy.HHmm')
}
ext.supportLibVersion = '25.3.1'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:appcompat-v7:$supportLibVersion"
compile "com.android.support:design:$supportLibVersion"
}
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/mudar/android-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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ca.mudar.rotationquicksetting">
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:resizeableActivity="true"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name="ca.mudar.rotationquicksetting.ui.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="ca.mudar.rotationquicksetting.service.QuickSettingsService"
android:icon="@drawable/ic_screen_lock_rotation"
android:label="@string/tile_label"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
</application>
</manifest>
\ No newline at end of file
package ca.mudar.rotationquicksetting;
/**
* Created by mudar on 10/05/17.
*/
public class Const {
/**
* SharedPreferences
*/
public static final String APP_PREFS_NAME = "orientation_tile_prefs";
public interface PrefsNames {
String REVERSE_PORT = "prefs_reverse_port";
String REVERSE_LAND = "prefs_reverse_land";
String AUTO_ROTATE = "prefs_auto_rotate";
String PERMISSION_GRANTED = "prefs_permission_granted";
String ABOUT = "prefs_about";
}
}
package ca.mudar.rotationquicksetting.data;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import ca.mudar.rotationquicksetting.Const;
import ca.mudar.rotationquicksetting.Const.PrefsNames;
import ca.mudar.rotationquicksetting.R;
/**
* Created by mudar on 10/05/17.
*/
public class UserPrefs {
private static UserPrefs instance;
private SharedPreferences mPrefs;
private SharedPreferences.Editor mPrefsEditor;
private UserPrefs(Context context) {
mPrefs = context.getSharedPreferences(Const.APP_PREFS_NAME, Context.MODE_PRIVATE);
}
public static UserPrefs getInstance(Context context) {
if (instance == null) {
instance = new UserPrefs(context);
}
return instance;
}
private SharedPreferences.Editor edit() {
if (mPrefsEditor == null) {
mPrefsEditor = mPrefs.edit();
}
return mPrefsEditor;
}
public static void setDefaults(Context context) {
PreferenceManager.setDefaultValues(context,
Const.APP_PREFS_NAME,
Context.MODE_PRIVATE,
R.xml.prefs_defaults,
false);
}
public boolean isReversePortrait() {
return mPrefs.getBoolean(PrefsNames.REVERSE_PORT, false);
}
public boolean isReverseLandscape() {
return mPrefs.getBoolean(PrefsNames.REVERSE_LAND, false);
}
public boolean hasAutoRotate() {
return mPrefs.getBoolean(PrefsNames.AUTO_ROTATE, false);
}
}
package ca.mudar.rotationquicksetting.service;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.WindowManager;
import ca.mudar.rotationquicksetting.R;
import ca.mudar.rotationquicksetting.data.UserPrefs;
import ca.mudar.rotationquicksetting.utils.OrientationUtils;
/**
* Created by mudar on 07/05/17.
*/
public class QuickSettingsService extends TileService {
private final static String TAG = "QuickSettingsService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public void onStartListening() {
super.onStartListening();
Log.i(TAG, "onStartListening: ");
try {
updateQuickSettingsTile(getCurrentOrientation(UserPrefs.getInstance(getApplicationContext()).hasAutoRotate()),
Settings.System.canWrite(getApplicationContext()));
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
}
@Override
public void onStopListening() {
super.onStopListening();
Log.i(TAG, "onStopListening: ");
}
@Override
public void onClick() {
Log.i(TAG, "onClick: ");
if (Settings.System.canWrite(getApplicationContext())) {
try {
final int newOrientation = toggleOrientation();
updateQuickSettingsTile(newOrientation, true);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
} else {
showPermissionNotification();
}
}
private void updateQuickSettingsTile(int orientation, boolean canWriteSettings) throws Settings.SettingNotFoundException {
Log.d(TAG, "updateQuickSettingsTile() called with: orientation = [" + orientation + "]");
final Tile tile = getQsTile();
tile.setState(canWriteSettings ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
if (OrientationUtils.isLandscape(orientation)) {
tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.ic_screen_landscape));
tile.setLabel(getString(R.string.tile_label_land));
} else if (OrientationUtils.isPortrait(orientation)) {
tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.ic_screen_portrait));
tile.setLabel(getString(R.string.tile_label_port));
} else {
tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.ic_screen_rotation));
tile.setLabel(getString(R.string.tile_label_auto_rotate));
}
tile.updateTile();
}
/**
* Toggles current location, and returns the new value.
* Requires a prior check of Settings.System.canWrite()
*
* @return new rotation
*/
private int toggleOrientation() throws Settings.SettingNotFoundException {
Log.i(TAG, "toggleOrientation: ");
final ContentResolver contentResolver = getContentResolver();
final UserPrefs userPrefs = UserPrefs.getInstance(getApplicationContext());
if (userPrefs.hasAutoRotate() &&
Settings.System.getInt(contentResolver, Settings.System.ACCELEROMETER_ROTATION) == 0) {
// Enable auto-rotation
Log.e(TAG, "Enable auto-rotation");
Settings.System.putInt(contentResolver, Settings.System.ACCELEROMETER_ROTATION, 1);
return OrientationUtils.ROTATION_AUTO;
}
final int oldOrientation = getCurrentOrientation(false);
final int newOrientation = OrientationUtils.getOppositeOrientation(oldOrientation, userPrefs);
Settings.System.putInt(contentResolver, Settings.System.ACCELEROMETER_ROTATION, 0);
Settings.System.putInt(contentResolver, Settings.System.USER_ROTATION, newOrientation);
return newOrientation;
}
private int getCurrentOrientation(boolean includeAutoRotate) throws Settings.SettingNotFoundException {
Log.i(TAG, "getCurrentOrientation: ");
final ContentResolver contentResolver = getContentResolver();
final boolean hasAccelerometer = (Settings.System
.getInt(contentResolver, Settings.System.ACCELEROMETER_ROTATION) == 1);
if (hasAccelerometer && includeAutoRotate) {
return OrientationUtils.ROTATION_AUTO;
}
return hasAccelerometer ? getAccelerometerOrientation() : getUserOrientation();
}
private int getUserOrientation() throws Settings.SettingNotFoundException {
Log.i(TAG, "getUserOrientation: ");
return Settings.System.getInt(getContentResolver(), Settings.System.USER_ROTATION);
}
private int getAccelerometerOrientation() throws Settings.SettingNotFoundException {
Log.i(TAG, "getAccelerometerOrientation: ");
final WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
return windowManager.getDefaultDisplay().getRotation();
}
/**
* Create and show a simple notification containing the received GCM message.
*/
private void showPermissionNotification() {
final Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
Uri.parse("package:" + getPackageName()));
final PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
0,
intent,
PendingIntent.FLAG_ONE_SHOT);
final Resources res = getApplicationContext().getResources();
final String contentTitle = res.getString(R.string.notify_permissions_title);
final String contentText = res.getString(R.string.notify_permissions_text);
final Drawable drawable = ContextCompat.getDrawable(getApplicationContext(), R.mipmap.ic_launcher);
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext())
.setSmallIcon(R.drawable.ic_screen_lock_rotation)
.setLargeIcon(bitmap)
.setColor(getColor(R.color.app_notification))
.setContentTitle(contentTitle)
.setContentText(contentText)
.setOngoing(true)
.setShowWhen(false)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setContentIntent(pendingIntent);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
.notify(0, builder.build());
}
}
package ca.mudar.rotationquicksetting.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.BottomSheetDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import ca.mudar.rotationquicksetting.R;
/**
* Created by mudar on 16/05/17.
*/
public class AboutBottomSheetFragment extends BottomSheetDialogFragment implements View.OnClickListener {
public static AboutBottomSheetFragment newInstance() {
return new AboutBottomSheetFragment();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_about, container, false);
setupListeners(view);
return view;
}
@Override
public void onClick(View v) {
final int id = v.getId();
if (id == R.id.about_credits) {
showWebsite(R.string.url_mudar_ca);
} else if (id == R.id.about_source_code) {
showWebsite(R.string.url_github);
} else if (id == R.id.about_rate_app) {
showWebsite(R.string.url_playstore);
}
}
private void setupListeners(View view) {
view.findViewById(R.id.about_credits).setOnClickListener(this);
view.findViewById(R.id.about_source_code).setOnClickListener(this);
view.findViewById(R.id.about_rate_app).setOnClickListener(this);
}
private void showWebsite(@StringRes int website) {
final Intent viewIntent = new Intent(Intent.ACTION_VIEW);
viewIntent.setData(Uri.parse(getResources().getString(website)));
startActivity(viewIntent);
}
}
package ca.mudar.rotationquicksetting.ui;
import android.app.Fragment;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import ca.mudar.rotationquicksetting.data.UserPrefs;
/**
* Created by mudar on 10/05/17.
*/
public class MainActivity extends AppCompatActivity implements
SettingsFragment.SettingsAboutCallback {
private final static String FRAGMENT_TAG_SETTINGS = "f_settings";
private final static String FRAGMENT_TAG_ABOUT = "f_about";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set default preferences
UserPrefs.setDefaults(getApplicationContext());
if (savedInstanceState == null) {
final Fragment fragment = SettingsFragment.newInstance();
getFragmentManager().beginTransaction()
.replace(android.R.id.content, fragment, FRAGMENT_TAG_SETTINGS)
.commit();
}
}
/**
* Implements SettingsFragment.SettingsAboutCallback
* Shows about the About BottomSheetDialogFragment
*/
@Override
public void onShowAbout() {
final AboutBottomSheetFragment bottomSheet = AboutBottomSheetFragment.newInstance();
bottomSheet.show(getSupportFragmentManager(), FRAGMENT_TAG_ABOUT);
}
}
package ca.mudar.rotationquicksetting.ui;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import ca.mudar.rotationquicksetting.BuildConfig;
import ca.mudar.rotationquicksetting.Const;
import ca.mudar.rotationquicksetting.Const.PrefsNames;
import ca.mudar.rotationquicksetting.R;
import static android.content.ContentValues.TAG;
import static ca.mudar.rotationquicksetting.utils.OrientationUtils.ROTATION_LAND;
import static ca.mudar.rotationquicksetting.utils.OrientationUtils.ROTATION_LAND_REVERSE;
import static ca.mudar.rotationquicksetting.utils.OrientationUtils.ROTATION_PORT;
import static ca.mudar.rotationquicksetting.utils.OrientationUtils.ROTATION_PORT_REVERSE;
/**
* Created by mudar on 10/05/17.
*/
public class SettingsFragment extends PreferenceFragment implements
SharedPreferences.OnSharedPreferenceChangeListener,
Preference.OnPreferenceClickListener {
public static SettingsFragment newInstance() {
return new SettingsFragment();
}
private SharedPreferences mSharedPrefs;
private Preference mPermissionGranted;
private SettingsAboutCallback mListener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof SettingsAboutCallback) {
mListener = (SettingsAboutCallback) context;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PreferenceManager pm = this.getPreferenceManager();
pm.setSharedPreferencesName(Const.APP_PREFS_NAME);
pm.setSharedPreferencesMode(Context.MODE_PRIVATE);
addPreferencesFromResource(R.xml.prefs_settings);
mPermissionGranted = findPreference(PrefsNames.PERMISSION_GRANTED);
mSharedPrefs = pm.getSharedPreferences();
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
mPermissionGranted.setOnPreferenceClickListener(this);
findPreference(PrefsNames.ABOUT).setOnPreferenceClickListener(this);
}
@Override
public void onResume() {
super.onResume();
setupSummaries();
}
@Override
public void onDestroy() {
super.onDestroy();
/**
* Remove the listener
*/
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
}
/**
* Implements SharedPreferences.OnSharedPreferenceChangeListener
*
* @param sharedPreferences
* @param key
*/
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PrefsNames.REVERSE_PORT.equals(key)) {
showTryRotationSnackbar(sharedPreferences.getBoolean(key, false) ?
ROTATION_PORT_REVERSE : ROTATION_PORT);
} else if (PrefsNames.REVERSE_LAND.equals(key)) {
showTryRotationSnackbar(sharedPreferences.getBoolean(key, false) ?
ROTATION_LAND_REVERSE : ROTATION_LAND);
}
}
/**
* Implements Preference.OnPreferenceClickListener
*
* @param preference
* @return
*/
@Override
public boolean onPreferenceClick(Preference preference) {
final String key = preference.getKey();
if (PrefsNames.PERMISSION_GRANTED.equals(key)) {
final Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
Uri.parse("package:" + getContext().getPackageName()));
startActivity(intent);
return true;
} else if (PrefsNames.ABOUT.equals(key) && mListener != null) {
Log.i(TAG, "onPreferenceClick: ABOUT");
mListener.onShowAbout();
return true;
}
return false;
}
private void setupSummaries() {
mPermissionGranted.setSummary(getPermissionGrantedSummary());
findPreference(PrefsNames.ABOUT).setSummary(getString(
R.string.prefs_version_title, BuildConfig.VERSION_NAME));
}
@StringRes
private int getPermissionGrantedSummary() {
return Settings.System.canWrite(getContext()) ?
R.string.prefs_permission_granted_on : R.string.prefs_permission_granted_off;
}
private void setOrientation(int orientation) {
if (!Settings.System.canWrite(getContext())) {
return;
}
final ContentResolver contentResolver = getContext().getContentResolver();
Settings.System.putInt(contentResolver, Settings.System.ACCELEROMETER_ROTATION, 0);
Settings.System.putInt(contentResolver, Settings.System.USER_ROTATION, orientation);
}
private void showTryRotationSnackbar(final int orientation) {
if (getView() == null || !Settings.System.canWrite(getContext())) {
return;
}
@StringRes int message;
switch (orientation) {
case ROTATION_PORT:
message = R.string.snackbar_try_rotation_port;
break;
case ROTATION_PORT_REVERSE:
message = R.string.snackbar_try_rotation_port_reverse;
break;
case ROTATION_LAND:
message = R.string.snackbar_try_rotation_land;
break;
case ROTATION_LAND_REVERSE:
message = R.string.snackbar_try_rotation_land_reverse;
break;
default:
return;
}
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG)
.setAction(R.string.btn_try_it, new View.OnClickListener() {
@Override
public void onClick(View v) {