From 2326c521997e014cef9d88a88f427bfac1a14791 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Wed, 23 Dec 2015 01:43:41 +0100 Subject: [PATCH] Implement support for termux.properties Also some symlink-to-storage improvements and experimenting with requesting read storage permission. --- .../java/com/termux/app/TermuxActivity.java | 82 ++++++++++++++---- .../java/com/termux/app/TermuxInstaller.java | 48 +++++++--- .../com/termux/view/TerminalKeyListener.java | 4 + .../java/com/termux/view/TerminalView.java | 5 +- app/src/main/res/raw/bell.ogg | Bin 0 -> 5090 bytes 5 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 app/src/main/res/raw/bell.ogg diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index dda72b7c..95be34ef 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -1,19 +1,8 @@ package com.termux.app; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.termux.R; -import com.termux.drawer.DrawerLayout; -import com.termux.terminal.TerminalSession; -import com.termux.terminal.TerminalSession.SessionChangedCallback; -import com.termux.view.TerminalKeyListener; -import com.termux.view.TerminalView; - +import android.Manifest; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; @@ -27,11 +16,15 @@ import android.content.DialogInterface.OnShowListener; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; +import android.media.AudioAttributes; +import android.media.SoundPool; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Vibrator; @@ -64,6 +57,19 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; +import com.termux.R; +import com.termux.drawer.DrawerLayout; +import com.termux.terminal.TerminalSession; +import com.termux.terminal.TerminalSession.SessionChangedCallback; +import com.termux.view.TerminalKeyListener; +import com.termux.view.TerminalView; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * A terminal emulator activity. * @@ -114,6 +120,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection */ boolean mIsVisible; + private SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes( + new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build(); + private int mBellSoundId; + private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -125,6 +136,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection } }; + /** For processes to access shared internal storage (/sdcard) we need this permission. */ + @TargetApi(Build.VERSION_CODES.M) + public void ensureStoragePermissionGranted() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1234); + } + } + } + @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); @@ -229,11 +250,22 @@ public final class TermuxActivity extends Activity implements ServiceConnection @Override public void onSingleTapUp(MotionEvent e) { - // Toggle keyboard visibility if tapping with a finger: - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); + switch (mSettings.mTapBehaviour) { + case TermuxPreferences.TAP_TOGGLE_KEYBOARD: + // Toggle keyboard visibility if tapping with a finger: + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); + break; + case TermuxPreferences.TAP_SHOW_MENU: + mTerminalView.showContextMenu(); + break; + } } + @Override + public boolean shouldBackButtonBeMappedToEscape() { + return mSettings.mBackIsEscape; + } }); findViewById(R.id.new_session_button).setOnClickListener(new OnClickListener() { @@ -294,7 +326,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection mTerminalView.checkForTypeface(); mTerminalView.checkForColors(); - TermuxInstaller.setupStorageSymlink(this); + ensureStoragePermissionGranted(); + + TermuxInstaller.setupStorageSymlinks(this); + + mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1); } /** @@ -351,7 +387,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection @Override public void onBell(TerminalSession session) { - if (mIsVisible) ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50); + if (mIsVisible) { + switch (mSettings.mBellBehaviour) { + case TermuxPreferences.BELL_BEEP: + mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f); + break; + case TermuxPreferences.BELL_VIBRATE: + ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50); + break; + } + + } } }; diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index a98a89e1..9265fed5 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; +import android.os.Environment; import android.system.Os; import android.util.Log; import android.util.Pair; @@ -200,31 +201,50 @@ final class TermuxInstaller { } } - public static void setupStorageSymlink(final Context context) { - final File[] dirs = context.getExternalFilesDirs(null); - if (dirs == null || dirs.length < 2) return; + public static void setupStorageSymlinks(final Context context) { new Thread() { public void run() { try { - final File externalDir = dirs[1]; File homeDir = new File(TermuxService.HOME_PATH); homeDir.mkdirs(); - File externalLink = new File(homeDir, "storage"); + File storageDir = new File(homeDir, "storage"); - if (externalLink.exists()) { - if (externalLink.getCanonicalPath().equals(externalDir.getPath())) { - // Keeping existing link. + if (storageDir.exists()) { + if (storageDir.isDirectory()) { return; } else { - // Removing old link to give place to new. - if (!externalLink.delete()) { - Log.e("termux", "Unable to remove old $HOME/storage to give place for new"); - return; - } + storageDir.delete(); } } - Os.symlink(externalDir.getAbsolutePath(), externalLink.getAbsolutePath()); + storageDir.mkdirs(); + + File sharedDir = Environment.getExternalStorageDirectory(); + Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath()); + + File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + Os.symlink(downloadsDir.getAbsolutePath(), new File(storageDir, "downloads").getAbsolutePath()); + + File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); + Os.symlink(dcimDir.getAbsolutePath(), new File(storageDir, "dcim").getAbsolutePath()); + + File documentsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + Os.symlink(documentsDir.getAbsolutePath(), new File(storageDir, "documents").getAbsolutePath()); + + File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + Os.symlink(picturesDir.getAbsolutePath(), new File(storageDir, "pictures").getAbsolutePath()); + + File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + Os.symlink(musicDir.getAbsolutePath(), new File(storageDir, "music").getAbsolutePath()); + + File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); + Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath()); + + final File[] dirs = context.getExternalFilesDirs(null); + if (dirs == null || dirs.length >= 2) { + final File externalDir = dirs[1]; + Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath()); + } } catch (Exception e) { Log.e("termux", "Error setting up link", e); } diff --git a/app/src/main/java/com/termux/view/TerminalKeyListener.java b/app/src/main/java/com/termux/view/TerminalKeyListener.java index 184d1d8e..83109b86 100644 --- a/app/src/main/java/com/termux/view/TerminalKeyListener.java +++ b/app/src/main/java/com/termux/view/TerminalKeyListener.java @@ -6,6 +6,8 @@ import android.view.ScaleGestureDetector; /** * Input and scale listener which may be set on a {@link TerminalView} through * {@link TerminalView#setOnKeyListener(TerminalKeyListener)}. + * + * TODO: Rename to TerminalViewClient. */ public interface TerminalKeyListener { @@ -17,4 +19,6 @@ public interface TerminalKeyListener { /** On a single tap on the terminal if terminal mouse reporting not enabled. */ void onSingleTapUp(MotionEvent e); + boolean shouldBackButtonBeMappedToEscape(); + } diff --git a/app/src/main/java/com/termux/view/TerminalView.java b/app/src/main/java/com/termux/view/TerminalView.java index d8fdec8a..415e9b68 100644 --- a/app/src/main/java/com/termux/view/TerminalView.java +++ b/app/src/main/java/com/termux/view/TerminalView.java @@ -12,7 +12,6 @@ import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; import android.annotation.SuppressLint; -import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Typeface; @@ -492,7 +491,7 @@ public final class TerminalView extends View { @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); - if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) { + if (keyCode == KeyEvent.KEYCODE_ESCAPE || (keyCode == KeyEvent.KEYCODE_BACK && mOnKeyListener.shouldBackButtonBeMappedToEscape())) { // Handle the escape key ourselves to avoid the system from treating it as back key // and e.g. close keyboard. switch (event.getAction()) { @@ -518,7 +517,7 @@ public final class TerminalView extends View { if (handleVirtualKeys(keyCode, event, true)) { invalidate(); return true; - } else if (event.isSystem() && keyCode != KeyEvent.KEYCODE_BACK) { + } else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) { return super.onKeyDown(keyCode, event); } diff --git a/app/src/main/res/raw/bell.ogg b/app/src/main/res/raw/bell.ogg new file mode 100644 index 0000000000000000000000000000000000000000..674f25d02621bf91e077455a824a8d723b0c1ddf GIT binary patch literal 5090 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