Initial commit

This commit is contained in:
aNNiMON 2024-02-13 23:53:58 +02:00
commit b5e775e520
23 changed files with 797 additions and 0 deletions

35
AndroidManifest.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.annimon.win1251viewer"
android:versionCode="1"
android:versionName="1.1" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity android:name=".FileBrowserActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ViewTextActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType ="text/*" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

17
ant.properties Normal file
View File

@ -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.

92
build.xml Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="Win1251Viewer" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

20
proguard-project.txt Normal file
View File

@ -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 *;
#}

40
proguard.cfg Normal file
View File

@ -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 <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(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 *;
}

11
project.properties Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

23
res/layout/file_item.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="2dp"
android:paddingLeft="10dp" >
<ImageView android:id="@+id/file_item_icon"
android:layout_width="35dp"
android:layout_height="35dp"
android:gravity="center"
android:scaleType="centerInside" />
<TextView android:id="@+id/file_item_name"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingLeft="3dp"
android:textSize="18sp" />
</LinearLayout>

15
res/layout/main.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.annimon.win1251viewer.ZoomingTextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars = "vertical"
/>
</LinearLayout>

4
res/values/strings.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Win1251Viewer</string>
</resources>

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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<String> 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<String>();
path = new ArrayList<String>();
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<File> {
public int compare(File file1, File file2) {
return file1.getName().compareToIgnoreCase(file2.getName());
}
}
}

View File

@ -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);
}
}

View File

@ -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<String> objects;
public FileListAdapter(Context context, List<String> 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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}