вторник, 26 ноября 2013 г.

HListView и ViewPager

Есть такой хороший контрол для андроида: HListView (либа: HorizontalVariableListView). Это горизонтальный ListView.

Есть, конечно Gallery. Но этот контрол считается deprecated, начиная с апи 16, плюс его логика такова, что он будет центровать дочерние элементы. Да и баг есть. Не очень корректно отрабатывается detachViewsFromParent, но это уже совсем другая история.

Главное, что HorizontalVariableListView со своей задачей справляется. Я как понимаю это просто взяли код ListView и адаптировали его. Т.е. там своя цепочка классов: HListView - AbsHListView - AdapterView, ну а дальше уже стандартный ViewGroup. Но жить это не мешает.

Интересная ситуация возникла, когда я поместил HListView внутри ViewPager. Были реализованы табы и на одном из них был такой контрол. Была даже более сложно: обычный ListView, каждый элемент которого - это в свою очередь HListView.

Всё работало хорошо на 4-ке. Когда же попробовал на 2-ке. Балалайка: вместо скролинга элемента ListView, переворачивалась другая страница ViewPager. Проблема была бы у любого девайса с апи < 14.

Одно из решений: перегрузить canScroll. Вот источник на SO
@Override
 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
  return super.canScroll(v, checkV, dx, x, y) || (checkV && customCanScroll(v));
 }

 protected boolean customCanScroll(View v) {
  return (v instanceof HorizontalScrollView || v instanceof AbsHListView);
 }

В данном случае решение SO чуть подправлено для правильный работы.

Суть проблемы заключается в том, что в реализации canScroll ViewPager есть строка:
ViewCompat.canScrollHorizontally(v, -dx);
canScrollHorizontally всегда возвращает false, и только с 14 сдк метод перегружен и возвращается View::canScrollHorizontally

суббота, 8 июня 2013 г.

Вертикальный TextView в Android

VerticalTextView.java

public class VerticalTextView extends TextView {

 private int _width, _height;
 private final Rect _bounds = new Rect();

 public VerticalTextView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
 }

 public VerticalTextView(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public VerticalTextView(Context context) {
  super(context);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  // vise versa
  _height = getMeasuredWidth();
  _width = getMeasuredHeight();
  setMeasuredDimension(_width, _height);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  canvas.save();

  canvas.translate(_width, _height);
  canvas.rotate(-90);

  TextPaint paint = getPaint();
  paint.setColor(getTextColors().getDefaultColor());

  String text = text();

  paint.getTextBounds(text, 0, text.length(), _bounds);
  canvas.drawText(text, getCompoundPaddingLeft(), (_bounds.height() - _width) / 2, paint);

  canvas.restore();
 }

 private String text() {
  return super.getText().toString();
 }
}

xml:

    <com.my.namespace.VerticalTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|center_vertical"
        android:background="@color/feedback_background"
        android:padding="4dip"
        android:text="@string/feedback"
        android:textColor="@color/feedback_text_color"
        android:textSize="@dimen/text_xlarge" />

PS: начиная с API11 никакого огорода городить не надо. Используем setRotation

Поисковые системы в моём Google Chrome


Открываем chrome://settings/.  Затем управление поисковыми системами.

4pda: http://4pda.ru/forum/index.php?act=Search&source=all&forums[]=281&query=%s&x=17&y=16&subforums=1
dns: http://smolensk.dns-shop.ru/search/?q=%s
enter: http://www.enter.ru/search?q=%s
elmall67: http://elmall67.ru/search?l=1&search_field=%s&search_button=%CD%E0%E9%F2%E8
nnm-club: http://nnm-club.ru/forum/search.php?mode=results&show_results=topics&search_keywords=%s
pc67: http://pc67.ru/search?str=%s
rutracker: http://rutracker.org/forum/tracker.php?max=1&nm=%s
so: http://stackoverflow.com/search?q=%s
wiki: http://ru.wikipedia.org/wiki/%s
Перевод en > ru: http://translate.google.com/#en|ru|%s
Перевод ru > en http://translate.google.com/#ru|en|%s
yandex maps: http://maps.yandex.ru/?text=%s
yandex market: http://market.yandex.ru/search.xml?text=%s&from=os
Google play: https://play.google.com/store/search?q=%s

суббота, 11 мая 2013 г.

ASUS RT-N56U

Тема на хоботе: http://forum.ixbt.com/topic.cgi?id=14:58751-94
FAQ: http://forum.ixbt.com/topic.cgi?id=14:58751
Firmare: https://code.google.com/p/rt-n56u/downloads/list
Wiki: https://code.google.com/p/rt-n56u/w/list

Подготовка HDD:
cat /proc/partitions
fdisk -lu /dev/sda
fdisk /dev/sda:  d n p 1 w q
mkfs.ext4 -m 0 -T largefile /dev/sda1

mkdir /media/AiDisk_a1/opt




Скрипты монтирования/размонтирования:

/usr/bin/opt-mount.sh
/usr/bin/opt-umount.sh (2 параметра: devname - в виде /dev/sd[a-z][0-9], mpname - точка монтирования).

Zappa: Маленькие скриптики для того, что бы можно было сохранять/восстанавливать какие-то файлы кроме ключа админа после перезагрузки роутера.

В папке /etc/storage делаем файл, скажем persisted-files. туда складываем абсолютные пути от / всех файлов и папок, которые хотим восстанавливать после перезагрузки. например, мы добавили пользователя username и положили в его домашнюю директорию /home/username rsa ключи для беспарольного логина. Тогда содержимое файла persisted-files будет

код
/etc/passwd
/etc/group
/home/username

В файл /etc/storage/started_script.sh добавляем такие три строчки:

if [ -r /etc/storage/persisted.tar.bz2 ]; then
    tar -x -j -f persisted.tar.bz2 -C
fi

Запускаем команду

tar -c -f /etc/storage/persisted.tar.bz2 -j -T /etc/storage/persisted-list

Запускаем команду mtd_storage.sh save




Что сохраняется при вызове /sbin/mtd_storage.sh save?
Все содержимое /etc/storage. Оно также автоматом сохраняется при перезагрузке (если были изменения).
authorized_keys должно быть там, оно автоматически копируется туда куда нужно при старте. Но для основного пользователя.
Хотите добавить свою логику? В /etc/storage есть скрипты, в том числе 2 стартовых, там можете написать любую логику.

воскресенье, 7 апреля 2013 г.

Facebook API

Речь пойдет про использование facebook api для мобильного приложения под андроид. Если вы взялись за api, причём не важно для чего, знайте - вы вляпались. Много багов, ограничений, нюансов.

Я не буду останавливаться на элементарных вещах, как получение api key, отправка запросов и прочее, про которые можно узнать, почитав доки. Интересны нюансы, обычно, в них проблемы.

Использую в подавляющем большинстве FQL, а не Graph API. Потому что FQL позволяет запрашивать только нужные поля, которые надо, или достаточно сложные запросы, как: получить все альбомы моих друзей или получить все мероприятия, в которых среди участников есть определённый юзер. Но совсем без Graph API не обойтись. К примеру я не знаю, как получить документы групп с помощью FQL.

Первое, что надо уяснить, свести к минимуму количество запросов к серверу. Потому что у facebook есть определённые ограничения на максимальное количество запросов в определенный интервал времени. В один прекрасный момент просто начнут приходить json-ы с ошибкой. И придется ждать время. Понятно, что на родное приложение facebook с api key 350685531728 это не распространяется.

Часто встречаются ограничения на количество возвращаемых данных, limit в этом случае игнорируется:
- список тредов (50)?
- список альбомов, где фиды альбомов получаются выборкой из другой batch данного запроса. Возвращает только 200 альбомов.
"fids": SELECT object_id FROM album WHERE owner = ...
"albums: "SELECT object_id,name,... FROM album WHERE object_id IN (SELECT object_id FROM #fids)
- список фото по списку фидов альбомов: нельзя указывать больше 50 фидов.
SELECT album_object_id, object_id, caption,... FROM photo WHERE album_object_id IN (...)
- список всех альбомов по списку юзеров. На тестовом аккаунте: друзей ~800. Альбомов ~9000. Если же сделать запрос ниже, то вернёт ~2000 альбомов для ~70 юзеров.
SELECT object_id,owner FROM album WHERE owner IN (SELECT uid2 FROM friend WHERE uid1=me()) OR owner = me()

Мне нужно было получить документы всех групп. Для этого использовал batch requests, где каждая из 50 batch запрашивала документы какой-то одной группы. По непонятной причине, иногда ответ получается "урезанный", большинство batch пустые. Если же следом отправить такой же запрос, то всё корректно.

Не корректно работают тэги для видео. Если запрашивать их через graph api как: video_id/tags, то возвращает не всех юзеров. Хотя, если использовать fql: SELECT subject FROM video_tag WHERE vid = video_id, то вижу всех юзеров.

 недописано...

вторник, 5 марта 2013 г.

Как добавлял поддержку GDrive в android проект

Была уже поддержка dropbox, box.com. Настала очередь и google drive. Как никак продукт от самого гугла, а они умеют продвигать, что им надо. Правда потом так же успешно закрывать. Но не об это сейчас.

Все эти ребята как любят делать. Генеришь определенный урлик с app_id и открываешь в браузере, вводишь свои данные, а на выходе тебе токен. Хорошо, конечно, но хотелось бы лучше, когда просто открывается активити, юзер вводит логин, пароль, кликает "логин", в фоне логиниться и продолжает работать. Фактически надо повторить определенную последовательность GET/POST запросов и дело в шляпе. Так было с обоими первыми хранилищами (да и с facebook тоже).

С гуглом такое не прошло. Они юзают js, а через обычный явовский http клиент такое не с имитировать. Есть конечно, htmlunit и selenium, которые позволяют заюзать, вроде бы, без gui-шный браузер. Но сам не проверял, да и весят прилично. На данный момент отказался.
Можно было пойти законным путем для андроида. Но, честно не хотелось тянуть их либы, которые постоянно обновляются, потом какие-то api помечаются как устаревшие. Ведь фактически gdrive вышел из google docs. Тем более, что мне не так и много надо было операций (dir, upload, download, create folder, delete file/folder). Вполне можно было обойтись обычными GET/POST/DELETE запросами с передачей токенов.

Как ни странно, столкнулся с проблемой. Токен получался из аккаунта гугла, что подключен на девайсе, но он не был привязан к app_id. Пример гугловский оказался не рабочий, там так же вываливалась ошибка о не настроенном доступе (впрочем так часто бывает с примерами, гугл здесь оказался не исключением). Поиск на stackoverflow показал, что дело всё-таки в бабине. Плюс нашелся на github рабочий вариант.

Но как-то уже расхотелось юзать андроидовский вариант. Тем более нужны были тесты на яве. В итоге авторизацию стал делать как для stand alone application. Кратко: открываете в браузере определенный урлик, логинитесь, в ответ получить в теле html некоторый код, потом POST-запрос с этим кодом, в ответ же долгожданный accessToken и refreshToken. Токен живет 10 минут, потом его надо обновлять. Интересно, что после обновления приходит только accessToken, refreshToken остается прежним ( в том же box.com и refreshToken приходит новый). В моём выборе я был не одинок. Expert Explorer и ES Explorer использовали такой же механизм.
Ну а дальше уже юзал простые api.

Единственным нюансом оказалось удаление файлов/папок. Сперва использовал вариант через /files. Но был какой-то полукривой. Вроде объект удаляется, повторный вызов апи показывает, что объекта уже нет, а на вебе висит, хотя с ним ничего сделать нельзя. Через некоторое время он пропал и в вебе, но осадок остался. Второй вариант /children уже не доставил никаких неудобств.

В общем не без затыков, но задача была решена. Как часто бывает, спасибо докам гугла, достаточно информативно и понятно, плюс "Try it!" тоже хорошая вещь для отладки, посмотреть, какой там json генерится.

среда, 16 января 2013 г.

Почему сейчас я остановился на idrive.com?

Первое облачное хранилище, которое я попробовал было dropbox. Потом box.com, skydrive. Но всё это было не совсем то. Хотелось синхронизировать любые папки, а не только с какой-то одной, плюс не хотелось, чтобы любой сотрудник конторы хранилища имел доступ к моим данным. И самое главное, мне нужно было именно хранилище, т.е. синхронизация меня не особо интересовала.

В какой-то определенный момент я попробовал wuala. Почти по всем параметрам подходила, но тормознутость клиента на яве стала надоедать. В итоге сейчас я использую idrive.

idrive исключительно заточен на бэкап. С некоторого времени у них появился  idrive sync, который уже имеет функции синхронизации.

Даётся 5 Гб свободного места, можно указывать любые папки на диске (прямо через контекстное меню). Аплоад идет достаточно резво. Долго первый раз, потом уже отправляются только изменения, а не весь измененный файл (incremental backup).
И это очень радует. К примеру, есть от почтовой программы большой файл с письмами. Он занимает 600 Мб, и понятно, постоянно меняется, т.к. приходят новые письма. Замучиться можно ждать, если бы просто каждый раз аплодился такой большой файл. А за счет incremental backup заливается в считанные секунды. В целом бэкапится 10 Гб, в большинстве своём мелкие файлы. Если брать ситуацию, что почти ничего не изменилось на диске, то весь бэкап производится в течение пары минут.

Поддерживается до 30 версий изменений. Любую из них можно скачать из клиента. Я использую только windows. Хотя, если судить по сайту, также есть клиента под другие ОС, в том числе и мобильные. На данный момент версия клиента 6.x. Ничего лишнего. 

Данные в облака заливаются не в чистом виде, а шифруются, для этого при регистрации вводится помимо логина/пароля и ключ шифрования. Забывать его крайне не рекомендуется, иначе данные будут потеряны безвозвратно.

Важный момент - idrive выполняет бэкап, соответственно, если удалить файл на диске, он будет продолжать присутствовать в облаке. Если же надо подчистить облако и привести в соответствие с локальными данными, нужно использовать кнопку Archive Cleanup, в результате чего выполнится чистка и освободится место после удаления. При необходимости можно задать периодичность, с которой чистка будет выполняться в автоматическом режиме. Заметил такой баг, не баг, но не доработку. Когда кликаешь на cleanup, сперва производится генерация списка локальных данных, а после уже открывается окно, где показывается список файлов/папок которые будут удалены из облака. Показывается оно как не модальное и, видимо, запоминает позицию, где именно предыдущий раз показывалось. До этого у меня основное окно было распахнуто на весь экран, потом я его сместил вправо. В итоге окно со списком удаления показывалось за экран. Пока не распахнул и не сместил в бок.

Действуют реферальные ссылки. За каждую регистрацию по такой ссылке вы получите дополнительно 1 Гб. Внимание. Все мои ссылки на idrive реферальные. Дополнительный объем можно приобрести как для частного лица, так и для конторы. Цены смотреть здесь. Обычному юзеру можно купить 150 Гб за 5$ в месяц. Также за установку клиента дали 0.5 Гб. Или ввели новое правило или просто с задержкой приличной это делают. Потому что зарегистрировался и поставил клиент достаточно давно, приплюсовали к свободному месту месяцев через 6. Но вполне возможно, что это просто мне не повезло. Также можно пройти дополнительные миниквесты (лайкнуть в facebook, сделать твит и поставить мобильную версию приложения), тем самым получив ещё по 0.5 Гб. 

Работать с облаком можно и без клиента через браузер. Но ничего скачать не удастся  пока не введешь ключ шифрования. У меня, правда, нужды нет делать что-либо через браузер.

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

При удалении из облака данных они помещаются в корзину. После 30 дней эти данные будут удалены навсегда и из корзины.

idrive поддерживает расписание. По времени, дням недели. При необходимости можно включить опцию отправки на почту письма после завершения резервирования.

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

Расшарить объект можно через веб. Правда с одним нюансом, если будет использован ключ шифрования по умолчанию. Это и понятно, idrive ведь не знает наш ключ, а на сервере хранятся данные только в зашифрованном виде.

idrive также поддерживает как и остальные поиск, логи, резервирование в реальном времени.

Посмотреть ЧАВО можно здесь, а справку здесь.

UPD: маленькая ремарка :-) Отказался от idrive и теперь все данные лежат/бекапятся на домашнем My Book Live NAS.