commit b5e775e520908af0a5147bc7cae0104ac96ef8d4 Author: aNNiMON Date: Tue Feb 13 23:53:58 2024 +0200 Initial commit diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..187cfa8 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ant.properties b/ant.properties new file mode 100644 index 0000000..b0971e8 --- /dev/null +++ b/ant.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..a1be190 --- /dev/null +++ b/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# 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 *; +#} diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..b1cdf17 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..895c9ce --- /dev/null +++ b/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-16 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..49b3e1d Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-hdpi/icon_file.png b/res/drawable-hdpi/icon_file.png new file mode 100644 index 0000000..4692f5a Binary files /dev/null and b/res/drawable-hdpi/icon_file.png differ diff --git a/res/drawable-hdpi/icon_folder.png b/res/drawable-hdpi/icon_folder.png new file mode 100644 index 0000000..f495b29 Binary files /dev/null and b/res/drawable-hdpi/icon_folder.png differ diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..bd28a44 Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..73bf72f Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/layout/file_item.xml b/res/layout/file_item.xml new file mode 100644 index 0000000..91ca2aa --- /dev/null +++ b/res/layout/file_item.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 0000000..fd39f80 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..b8e584e --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Win1251Viewer + diff --git a/src/com/annimon/text/CP1251Encoding.java b/src/com/annimon/text/CP1251Encoding.java new file mode 100644 index 0000000..9bbee46 --- /dev/null +++ b/src/com/annimon/text/CP1251Encoding.java @@ -0,0 +1,45 @@ +/* + * aNNiMON 2012 + * For more info visit http://annimon.com/ + */ +package com.annimon.text; + +/** + * + * @author aNNiMON + */ +public class CP1251Encoding extends Encoding { + + /** Таблица кодировки "windows-1251" */ + protected static char[] cp1251 = { + '\u0410', '\u0411', '\u0412', '\u0413', '\u0414', '\u0415', '\u0416', + '\u0417', '\u0418', '\u0419', '\u041A', '\u041B', '\u041C', '\u041D', + '\u041E', '\u041F', '\u0420', '\u0421', '\u0422', '\u0423', '\u0424', + '\u0425', '\u0426', '\u0427', '\u0428', '\u0429', '\u042A', '\u042B', + '\u042C', '\u042D', '\u042E', '\u042F', '\u0430', '\u0431', '\u0432', + '\u0433', '\u0434', '\u0435', '\u0436', '\u0437', '\u0438', '\u0439', + '\u043A', '\u043B', '\u043C', '\u043D', '\u043E', '\u043F', '\u0440', + '\u0441', '\u0442', '\u0443', '\u0444', '\u0445', '\u0446', '\u0447', + '\u0448', '\u0449', '\u044A', '\u044B', '\u044C', '\u044D', '\u044E', + '\u044F' + }; + + public char decodeChar(byte b) { + int ich = b & 0xff; + if (ich == 0xb8) return 0x0451; // ё + else if (ich == 0xa8) return 0x0401; // Ё + else if (ich >= 0xc0) return cp1251[ich - 192]; + return (char) ich; + } + + public byte encodeChar(char ch) { + if (ch > 0 && ch < 128) return (byte) ch; + else if (ch == 0x401) return -88; // Ё + else if (ch == 0x404) return -86; // Є + else if (ch == 0x407) return -81; // Ї + else if (ch == 0x451) return -72; // ё + else if (ch == 0x454) return -70; // є + else if (ch == 0x457) return -65; // ї + return (byte) (ch + 176); + } +} diff --git a/src/com/annimon/text/Encoding.java b/src/com/annimon/text/Encoding.java new file mode 100644 index 0000000..f51385a --- /dev/null +++ b/src/com/annimon/text/Encoding.java @@ -0,0 +1,52 @@ +/* + * aNNiMON 2012 + * For more info visit http://annimon.com/ + */ +package com.annimon.text; + +import java.io.UnsupportedEncodingException; + +/** + * + * @author aNNiMON + */ +public abstract class Encoding { + + protected String encoding; + + public abstract char decodeChar(byte bt); + + public abstract byte encodeChar(char ch); + + /* + * Кодировать строку s в кодировку enc + */ + public byte[] encodeString(String s, String enc) throws UnsupportedEncodingException { + byte[] bs; + try { + bs = s.getBytes(enc); + } catch (UnsupportedEncodingException x) { + bs = new byte[s.length()]; + for (int i = 0; i < s.length(); i++) { + bs[i] = encodeChar(s.charAt(i)); + } + } + return bs; + } + + /* + * Декодировать участок массива b длиной len со смещения off из кодировки enc + */ + public String decodeString(byte[] bs, int off, int len, String enc) throws UnsupportedEncodingException { + try { + String s = new String(bs, off, len, enc); + return s; + } catch (UnsupportedEncodingException x) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append( decodeChar(bs[off + i]) ); + } + return sb.toString(); + } + } +} diff --git a/src/com/annimon/win1251viewer/FileBrowser.java b/src/com/annimon/win1251viewer/FileBrowser.java new file mode 100644 index 0000000..1104c08 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileBrowser.java @@ -0,0 +1,151 @@ +package com.annimon.win1251viewer; + +import android.app.ListActivity; +import android.os.Environment; +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * File browser. + * @author aNNiMON + */ +public class FileBrowser { + + // Comparator for sorting files. + private final FilesComparator filesComparator; + // Filter for directories and files. + private final FileFilter dirFilter, fileFilter; + + private String startDir; + private ListActivity activity; + + private FileOpenEventListener fileOpenEventListener; + + private List item, path; + private File currentDir; + + public FileBrowser(ListActivity activity) { + this(activity, Environment.getExternalStorageDirectory().getPath()); + } + + public FileBrowser(ListActivity activity, String startDir) { + this.activity = activity; + this.startDir = startDir; + + item = new ArrayList(); + path = new ArrayList(); + + filesComparator = new FilesComparator(); + dirFilter = new FileFilter() { + // Filter only readable directories. + public boolean accept(File file) { + return (file.isDirectory() && !file.isHidden() && file.canRead()); + } + }; + fileFilter = new FileFilter() { + // Filter only readable files. + public boolean accept(File file) { + return (!file.isDirectory() && !file.isHidden() && file.canRead()); + } + }; + // Begin scan files. + scanDirectory(startDir); + } + + public void setFileOpenEventListener(FileOpenEventListener fileOpenEvent) { + this.fileOpenEventListener = fileOpenEvent; + } + + public String getCurrentDir() { + return currentDir.getAbsolutePath(); + } + + public void setCurrentDir(String path) { + scanDirectory(path); + } + + public void itemSelected(int index) { + File file = new File(path.get(index)); + + if (file.isDirectory()) { + scanDirectory(path.get(index)); + return; + } + + if (fileOpenEventListener != null) { + fileOpenEventListener.onFileOpen(file); + } + } + + public void upDirectory() { + if (currentDir.getPath().equals(startDir)) { + activity.finish(); + } else { + scanDirectory(currentDir.getParent()); + } + } + + public void rescanCurrentDirectory() { + scanDirectory(currentDir.getPath()); + } + + private void scanDirectory(String dirPath) { + activity.setTitle(dirPath); + + item.clear(); + path.clear(); + + File f = new File(dirPath); + currentDir = f; + + if (!dirPath.equals(startDir)) { + item.add("../"); + path.add(f.getParent()); + } + + addDirectories(f); + addFiles(f); + + FileListAdapter fileListAdapter = new FileListAdapter(activity, item); + activity.setListAdapter(fileListAdapter); + } + + private void addDirectories(File file) { + File[] directories = file.listFiles(dirFilter); + sortFiles(directories); + + for (int i = 0; i < directories.length; i++) { + File f = directories[i]; + path.add(f.getPath()); + item.add(f.getName() + "/"); + } + } + + private void addFiles(File file) { + File[] files = file.listFiles(fileFilter); + sortFiles(files); + + for (int i = 0; i < files.length; i++) { + File f = files[i]; + path.add(f.getPath()); + item.add(f.getName()); + } + } + + private void sortFiles(File[] files) { + Arrays.sort(files, filesComparator); + } + + + private class FilesComparator implements Comparator { + + public int compare(File file1, File file2) { + return file1.getName().compareToIgnoreCase(file2.getName()); + } + + } +} diff --git a/src/com/annimon/win1251viewer/FileBrowserActivity.java b/src/com/annimon/win1251viewer/FileBrowserActivity.java new file mode 100644 index 0000000..8115175 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileBrowserActivity.java @@ -0,0 +1,55 @@ +package com.annimon.win1251viewer; + +import android.app.ListActivity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; +import java.io.File; + +/** + * + * @author aNNiMON + */ +public class FileBrowserActivity extends ListActivity implements FileOpenEventListener { + + private FileBrowser fileBrowser; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + fileBrowser = new FileBrowser(this); + fileBrowser.setFileOpenEventListener(this); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putString("dir_path", fileBrowser.getCurrentDir()); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + String dir = savedInstanceState.getString("dir_path"); + fileBrowser.setCurrentDir(dir); + super.onRestoreInstanceState(savedInstanceState); + } + + @Override + protected void onListItemClick(ListView l, View v, int index, long id) { + fileBrowser.itemSelected(index); + } + + @Override + public void onBackPressed() { + fileBrowser.upDirectory(); + } + + public void onFileOpen(File openedFile) { + Intent intent = new Intent(this, ViewTextActivity.class); + intent.setData(Uri.fromFile(openedFile)); + startActivity(intent); + } +} diff --git a/src/com/annimon/win1251viewer/FileListAdapter.java b/src/com/annimon/win1251viewer/FileListAdapter.java new file mode 100644 index 0000000..93e0311 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileListAdapter.java @@ -0,0 +1,54 @@ +package com.annimon.win1251viewer; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import java.util.List; + +/** + * Adapter for listview [icon filename]. + * @author aNNiMON + */ +public class FileListAdapter extends BaseAdapter { + + private LayoutInflater inflater; + private List objects; + + public FileListAdapter(Context context, List objects) { + this.objects = objects; + this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public int getCount() { + return objects.size(); + } + + public Object getItem(int position) { + return objects.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + view = inflater.inflate(R.layout.file_item, parent, false); + } + + String item = (String) getItem(position); + // Set icon by filename type. + int iconId = item.endsWith("/") ? R.drawable.icon_folder : R.drawable.icon_file; + ((ImageView) view.findViewById(R.id.file_item_icon)).setImageResource(iconId); + // Set filename. + ((TextView) view.findViewById(R.id.file_item_name)).setText(item); + + return view; + } + +} diff --git a/src/com/annimon/win1251viewer/FileOpenEventListener.java b/src/com/annimon/win1251viewer/FileOpenEventListener.java new file mode 100644 index 0000000..833cdc2 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileOpenEventListener.java @@ -0,0 +1,12 @@ +package com.annimon.win1251viewer; + +import java.io.File; + +/** + * Listener for event, when user select a file. + * @author aNNiMON + */ +public interface FileOpenEventListener { + + void onFileOpen(File openedFile); +} diff --git a/src/com/annimon/win1251viewer/StringEncoder.java b/src/com/annimon/win1251viewer/StringEncoder.java new file mode 100644 index 0000000..27295f9 --- /dev/null +++ b/src/com/annimon/win1251viewer/StringEncoder.java @@ -0,0 +1,32 @@ +package com.annimon.win1251viewer; + + + +public class StringEncoder +{ + protected static char[] cp1251 = + { + '\u0410', '\u0411', '\u0412', '\u0413', '\u0414', '\u0415', '\u0416', + '\u0417', '\u0418', '\u0419', '\u041A', '\u041B', '\u041C', '\u041D', + '\u041E', '\u041F', '\u0420', '\u0421', '\u0422', '\u0423', '\u0424', + '\u0425', '\u0426', '\u0427', '\u0428', '\u0429', '\u042A', '\u042B', + '\u042C', '\u042D', '\u042E', '\u042F', '\u0430', '\u0431', '\u0432', + '\u0433', '\u0434', '\u0435', '\u0436', '\u0437', '\u0438', '\u0439', + '\u043A', '\u043B', '\u043C', '\u043D', '\u043E', '\u043F', '\u0440', + '\u0441', '\u0442', '\u0443', '\u0444', '\u0445', '\u0446', '\u0447', + '\u0448', '\u0449', '\u044A', '\u044B', '\u044C', '\u044D', '\u044E', + '\u044F' + }; + + public static char decodeCharCP1251 (byte b) + { + int ich = b & 0xff; + if (ich == 0xb8) // ё + return 0x0451; + else if (ich == 0xa8) // Ё + return 0x0401; + else if (ich >= 0xc0) + return cp1251[ich-192]; + return (char)ich; + } +} diff --git a/src/com/annimon/win1251viewer/ViewTextActivity.java b/src/com/annimon/win1251viewer/ViewTextActivity.java new file mode 100644 index 0000000..160e15d --- /dev/null +++ b/src/com/annimon/win1251viewer/ViewTextActivity.java @@ -0,0 +1,76 @@ +package com.annimon.win1251viewer; + +import android.app.*; +import android.net.*; +import android.os.*; +import android.text.method.ScrollingMovementMethod; +import android.view.*; +import java.io.*; + +public class ViewTextActivity extends Activity { + + private ZoomingTextView textView; + private boolean isUTF; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.main); + + isUTF = false; + String text = getText(isUTF); + textView = (ZoomingTextView) findViewById(R.id.textView); + textView.setMovementMethod(new ScrollingMovementMethod()); + textView.setTextSize(18); + textView.setText(text); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(Menu.NONE, 0, Menu.NONE, "UTF-8"); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + isUTF = !isUTF; + textView.setText(getText(isUTF)); + item.setTitle(!isUTF ? "UTF-8" : "WIN-1251"); + return true; + } + + private String getText(boolean utf) { + String text; + try { + Uri fileUri = getIntent().getData(); + InputStream is = new FileInputStream(fileUri.getPath()); + text = getText(is, utf); + is.close(); + } catch (Exception ex) { + text = "No data"; + } + return text; + } + + private String getText(InputStream is, boolean utf) throws IOException { + if (utf) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int oneByte; + while ((oneByte = is.read()) != -1) { + baos.write((byte) oneByte); + } + baos.flush(); + return new String(baos.toByteArray(), "UTF-8"); + } else { + StringBuilder sb = new StringBuilder(); + + int read; + while ((read = is.read()) != -1) { + sb.append(StringEncoder.decodeCharCP1251((byte) read)); + } + return sb.toString(); + } + } +} diff --git a/src/com/annimon/win1251viewer/ZoomingTextView.java b/src/com/annimon/win1251viewer/ZoomingTextView.java new file mode 100644 index 0000000..5c2f9e7 --- /dev/null +++ b/src/com/annimon/win1251viewer/ZoomingTextView.java @@ -0,0 +1,63 @@ +package com.annimon.win1251viewer; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.widget.TextView; + +/** + * + * @author aNNiMON + */ +public class ZoomingTextView extends TextView { + + private static final int DEFAULT_TEXT_SIZE = 18; + private static final int MINIMAL_TEXT_SIZE = 6, MAXIMAL_TEXT_SIZE = 50; + private static final float MIN_SCALE = MINIMAL_TEXT_SIZE / (float) DEFAULT_TEXT_SIZE; + private static final float MAX_SCALE = MAXIMAL_TEXT_SIZE / (float) DEFAULT_TEXT_SIZE; + + private ScaleGestureDetector scaleDetector; + private float scaleFactor; + + public ZoomingTextView(Context context) { + super(context); + init(context); + } + + public ZoomingTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public ZoomingTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + scaleDetector = new ScaleGestureDetector(context, new ScaleListener()); + scaleFactor = 1.0f; + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + scaleDetector.onTouchEvent(event); + return super.onTouchEvent(event); + } + + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + @Override + public boolean onScale(ScaleGestureDetector detector) { + scaleFactor *= detector.getScaleFactor(); + scaleFactor = Math.max(MIN_SCALE, Math.min(scaleFactor, MAX_SCALE)); + + final int textSize = (int) (scaleFactor * DEFAULT_TEXT_SIZE); + setTextSize(textSize); + + return true; + } + } +}