суббота, 12 января 2013 г.

Двигаем мышью


    При опробовании беспроводного тачпада Logitech в качестве альтернативы мыши возникла мысль – ведь рядом лежит вполне готовый беспроводной тачпад – смартфон. Мышь он, конечно, не заменит,  но кое-что сделать,  комфортно развалившись в кресле,  вполне можно.
Идея эта не нова, есть достаточно приложений для удаленного управления ПК с андроид-устройства.  Решил реализовать минималистический андроид-тачпад с настройкой чувствительности по осям и сглаживания движения, который в дальнейшем, при желании, можно наполнить разнообразными возможностями удаленного управления.

В качестве отправной точки используем ранее слепленную программу рисования каракулей на экране. Только теперь эти каракули и прочие околоэкранные конвульсии надо будет передать на ПК.

Серверную часть можно посмотреть тут:

Двигаемся плавно и естественно.

Хочу более подробно остановиться на эмуляции движения мыши на ПК.  Для эмуляции мышиных бегов используем класс Robot и его методы mouseMove, mousePress, mouseRelease. Больше всего проблем возникло при попытке заставить курсор мыши перемещаться достаточно быстро и плавно.

Опробовал вот такие методы:

1. Просто mouseMove – быстрые рывки, практически неприменимо.
2. Алгоритм Брезенхэма – при использовании задержки 1мс на каждом шаге отрабатывает слишком медленно.
3. Вычисление координат с использованием формулы отрезка прямой с заданным шагом – все равно дергается курсор.
4. Еще один пошаговый алгоритм с количеством шагов, вычисляемым как функция от кода сглаживания. Использовал задержку 1 мс на каждом втором шаге.

Самый лучший, но совсем не идеальный, результат получился при использовании варианта 4. Буду благодарен если кто-то подскажет более красивый вариант.

/**
 * Compute mouse step to move mouse
 * @param len Move distance
 * @param smooth Smoothing mode
 * @return Mouse step
 */
private static int getMouseStep(double len, int smooth){
    int a = 30;
    switch (smooth)
    {
        case 1:
            a = 30;
            if (len < 5) a = 2;
            break;
        case 2:
            a = 40;
            if (len < 5) a = 2;
            break;
        case 3:
            a = 50;
            if (len < 5) a = 2;
            break;
        case 4:
            a = 50;
            if (len > 50) a = 70;
            if (len < 5) a = 2;
            break;
        case 5:
            a = 70;
            if (len < 50) a = 50;           
            if (len < 5) a = 2;
            break;
        case 6:
            a = 90;
            if (len < 50) a = 70;           
            if (len < 5) a = 5;
            break;
        }
        return a;
    }
   

/**
 * Move mouse (actual version)
 * @param xstart Start X coordinate
 * @param ystart Start Y coordinate
 * @param xend Finish X coordinate
 * @param yend Finish Y coordinate
 * @param sm Smoothing
 * @param rb Mouse Robot
 */
private static void MouseMove5(int xstart, int ystart, int xend, int yend, int sm, Robot rb) {
   
    int a = 30;
    boolean flag;
   
    double len = Math.sqrt((xend-xstart)*(xend-xstart)+(yend-ystart)*(yend-ystart));
   
    if (len < 3){
        rb.mouseMove(xend, yend);
        rb.delay(1);
    }
       
    a=getMouseStep(len, sm);
    flag = true;
    for (int i = 0; i < a; i++) {
        int mov_x = ((xend * i) / a) + (xstart * (a - i) / a);
        int mov_y = ((yend * i) / a) + (ystart * (a - i) / a);
        rb.mouseMove(mov_x, mov_y);
        if (flag){
            rb.delay(1);
            flag = false;
        }
        else flag = true;
    }
    rb.mouseMove(xend, yend);
}
                                                                                                                                                                 

Вопросы безопасности.

При разработке или использовании систем удаленного управления надо помнить, что желающие удаленно порулить чужим компьютером найдутся всегда. 

При подключении к серверу  будем передавать хеш-код пароля. Серверное приложение при первом старте (и в режиме настройки) запросит ввод пароля и сохранит его хеш-код. Для вычисления хеш-кода используем алгоритм MD5:

**
 * Encrypt password
 * Create MD5 hash
 * @param input Input String
 * @return MD5 Hash String
 */
    public static String md5Encrypt(String input){
        String md5 = null;
       
        if (input == null)
            return null;
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(input.getBytes(), 0, input.length());
            md5 = new BigInteger(1, digest.digest()).toString(16);
        }
        catch(Exception e){
            e.printStackTrace();
        }
        return md5;
    }

В любом случае, я бы рекомендовал использовать такую программу исключительно в домашней wifi сети.


среда, 19 декабря 2012 г.

Анаграммы на Android

Как-то застал жену за придумыванием всех слов , которые можно составить из букв слова «лекарство» ( flash-игра такая), немного помог ей в этом.  Мысль об автоматизации возникла сразу, смысл игры при этом, конечно, теряется, но стало интересно  - насколько много слов можно составить и насколько быстро такой поиск будет выполняться на смартфоне.
Для начала нашел словарик (в текстовом формате) из  47612 существительных в единственном числе, именительном падеже . Словарь конечно  попался кривоватый – многих слов нет, зато есть, например, слово «та» (то ли русское местоимение, то ли буква арабского алфавита, то ли знак каны). Этот словарик будем загружать в массив строк при старте программы.
Для введенного слова посчитаем количество для каждой из букв. Для каждой буквы словарного слова тоже будем считать количество и, если для всех букв словарного слова это количество будет меньше или равно аналогичному  значению для введенного слова, то словарное слово нам подходит.  Частным случаем тут будет поиск анаграмм – когда найденное словарное слово имеет ту же длину,  что и исходное. Возможно, существует и более производительный алгоритм, но реализовал то, что сразу пришло в голову.
В итоге для слова «лекарство» за 3 секунды на HTC Desire было найдено 175 вариантов.
Ниже исходник получившейся программы.

/**
 * Anagram Builder
 * Copyright (C) 2012 Anmyst
 * @author Anmyst
 * @version 1.0
*/

package com.anmyst.anagramru;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

/**
 * Anagram Builder activity
 */
public class AnagramruActivity extends Activity {

 private ArrayList <String> arr; //словарь
 private ArrayAdapter <String> adapt; //список результатов
 private HashMap map; // хранит количество букв для каждой буквы в слове

 /**
  * Запуск поиска анаграмм
  */
 private OnClickListener ButtonStart_click = new View.OnClickListener(){
 
  @Override
  public void onClick(View v) {
   final EditText eWord = (EditText)findViewById(R.id.editTextWord);
   String sWord = eWord.getText().toString();
   if (sWord.equalsIgnoreCase(""))
    return;
  
   String sDict;
   adapt.clear();
   map.clear();
  
   for(int i = 0; i < sWord.length(); i++){
    map.put(sWord.charAt(i), CharCount(sWord,sWord.charAt(i)));
   }
  
   boolean anagram = false;
   final CheckBox chkAnagram = (CheckBox)findViewById(R.id.checkBoxAnagram);
   if (chkAnagram.isChecked())
    anagram = true;
  
   int key;
   int ok;
   int counter = 0;
   for(int j = 0; j < arr.size(); j++){
    sDict = arr.get(j);
    ok = 1;
    for (int k = 0; k < sDict.length(); k++){
    
     if(map.containsKey(sDict.charAt(k)))
      key = (Integer)map.get(sDict.charAt(k));
     else
      key = 0;
     if (CharCount(sDict, sDict.charAt(k)) > key){
      ok = 0;
      continue;
     }
    }
    if (ok == 1){
     if ((!anagram) || (sDict.length() == sWord.length())){     
       adapt.add(sDict);
       counter++;     
     }     
    }
    
   }
   showToast("Found:" + counter);
  
  }
 };

 /**
  * Вывод короткого сообщения
  * @param s Строка сообщения
  */
 private void showToast(String s){ 
  Toast mess = Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT);
  mess.show(); 
 }

 /**
  * Считает количество заданных символов в строке
  * @param s исходное слово
  * @param c символ для которого расчитывается количество
  * @return количество заданных символов в слове
  */
 private int CharCount(String s, char c){
  int cnt = 0;
  for (int i = 0; i < s.length(); i++){
   if (s.charAt(i) == c)
    cnt++;
  }
  return cnt;
 }

 /**
  * Чтение строк из файла словаря
  * в массив arr
  * @param fname имя файла
  */
 private int ReadFile(String fname)
 {
  String s1;
  try{
   File f = new File(Environment.getExternalStorageDirectory() + "/DictRu/" + fname);
   FileInputStream fileIS = new FileInputStream(f);
   BufferedReader buf = new BufferedReader(new InputStreamReader(fileIS, "windows-1251"));
   String readString = new String();
   while((readString = buf.readLine()) != null){
    s1 = readString.toLowerCase();
    arr.add(s1);
   }
  } catch (FileNotFoundException e) {
      e.printStackTrace();
      return 1;
   } catch (IOException e){
    e.printStackTrace();
      return 2;
   }
  return 0;
 }

 /**
  * Загрузить словарь из файла
  */
 private void LoadDictionary(){
  arr.clear();
  if (ReadFile("dictru.txt") != 0){
   showToast("Load Dictionary Error!");
  }
  else{
   showToast("Dictionary loaded!");
  }
  return;
 }

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
   
    final Button bStart = (Button)findViewById(R.id.buttonStart);
    bStart.setOnClickListener(ButtonStart_click);
   
    map = new HashMap();
   
    arr = new ArrayList <String>();
    adapt = new ArrayAdapter<String> (this,android.R.layout.simple_list_item_1,android.R.id.text1);
    adapt.setNotifyOnChange(true);
   
    ListView list = (ListView)this.findViewById(R.id.listViewResults);
    list.setAdapter(adapt);
   
    LoadDictionary();
}

}