Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 48484 сообщений
Cообщения с меткой

android - Самое интересное в блогах

Следующие 30  »
protrizu

Как отформатировать Андроид

Суббота, 01 Октября 2016 г. 11:46 (ссылка)

Со временем работа мобильного устройства замедляется, смартфон может начать работать не совсем корректно, часто перезагружаться и плохо функционировать. В том числе это касается и аппаратов под управлением операционной системы Android. Чтобы исправить данную проблему, необходимо отформатировать устройство и его съемную память. ЧИТАТЬ ДАЛЬШЕ>>>



Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
nisttawe

Как обновляют ОС Android

Суббота, 01 Октября 2016 г. 11:15 (ссылка)

Большинство устройств, работающих под управлением операционной системы Android, имеют функцию автоматического обновления ОС. Эта технология избавляет пользователя от необходимости самому искать и загружать необходимые дополнения. ЧИТАТЬ ДАЛЬШЕ>>>



Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Google сделал рассылку пользователям о возможности изменения поисковой системы по умолчанию

Пятница, 30 Сентября 2016 г. 16:16 (ссылка)

С чего все началось.



Пользователи Android начали получать уведомление о том, что могут изменить поисковую систему, установленную по умолчанию, устанавливать поисковые виджеты, а также деактивировать сервисы от Google.



Яндекс победил Google

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/311442/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
namsionihor

Как настроить wi-fi на Андроиде

Суббота, 01 Октября 2016 г. 05:14 (ссылка)

Многие устройства, работающие под управлением операционной системы Android, способны подключаться к беспроводным сетям. Для обеспечения данной функции необходимо правильно настроить параметры соединения с точками доступа. ЧИТАТЬ ДАЛЬШЕ>>>



Комментарии (0)КомментироватьВ цитатник или сообщество
smarsouthnuvil

Как отформатировать Андроид

Пятница, 30 Сентября 2016 г. 17:46 (ссылка)

Со временем работа мобильного устройства замедляется, смартфон может начать работать не совсем корректно, часто перезагружаться и плохо функционировать. В том числе это касается и аппаратов под управлением операционной системы Android. Чтобы исправить данную проблему, необходимо отформатировать устройство и его съемную память. ЧИТАТЬ ДАЛЬШЕ>>>



Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Программируйте там, где затык будет, а не там, где он был

Пятница, 30 Сентября 2016 г. 10:39 (ссылка)

В 2013 году от рождества Христова мысль, что телефоны с ARM-профессорами будут запускать полноценный JavaScript также быстро, как десктопы, оснащенные x86, вызывала смех. В те старые времена, три года назад, iPhone 5 отставал по мощности примерно в 10 раз. Казалось, что ничего не может измениться в ближайшее время.



Но всё изменилось. Новый айфон 7 запускает JavaScript, согласно измерениям JetStream benchmark, БЫСТРЕЕ, чем самый быстрый на сегодняшний день Макбук (не про и не эйр). Лучший 5K iMac с 4Ггц процессором i7 теперь всего в два раза быстрее 7го айфона в этом тесте. Процессоры ARM улучшаются с совершенно безумной скоростью. Мур расслабился с десктопами, но бежит как сумасшедший в мобильном мире.



img



Скорость прогресса пристыдила многие предсказания будущего, но этот конкретный пример все же поражает. Три года назад — это не так давно! С того времени мы пришли от "на порядок хуже" до "быстрее, чем большинство лаптопов".



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



Вот цитата из 2013:



Уточню: на телефоне возможно сделать совместную работу в реальном времени. Но это просто невозможно с JavaScript. Разница в производительности между нативными и веб-приложениями сравнима с разницей между FireFox and IE8: она слишком большая для серьезной работы.

Разницы больше нет. Так что, видимо, теперь айфон 7 официально подходит для Серьезной Работы ;-)



И вот что самое смешное. В 2013 мы сделали приложение под айфон для нашего инструмента совместной работы Basecamp. Мы использовали JavaScript и веб в комбинации. Мы любим веселиться на работе, но мне кажется, что результат был все же Достаточно Серьезным.



Мы использовали мобильный веб в самом сердце наших нативных приложений, и в то время это был рисковый шаг. Шрам от Фейсбука, который отказался от HTML5 в пользу чистого натива в 2012 году, был все еще слишком свеж в памяти тех, кто работал на пересечении веба и нативного кода. И, честно говоря, пришлось идти на компромиссы. Все было не так быстро, как в нативном варианте, но было достаточно быстрым.



И это было во времена "разницы на порядок"! Сегодня производительность этой стратегии не просто достаточно хорошая, она настолько высокая, что может быть офигенной. Или, иными словами, не является проблемой.



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



Я не говорю, что гибридный подход не приводит к компромиссам. Все еще есть некоторые моменты, которые ощущаются менее нативно, и им не хватает этой маленькой детали чтобы быть идеальными. И, конечно же, есть приложения, вроде крытых 3D-игр, где нужно выдавить все возможные капли производительности. Но в сегодняшних условиях количество приложений, которые можно создать таким гибридным веб/нативным подходом, и которые будут просто офигеть какие классные, несомненно очень большое. Это число намного, намного больше, чем в 2013 году.



Преимущества в продуктивности при разработке мультиплатформенных сервисов с помощью гибридного подхода — поразительны. Мы бы просто не смогли сделать Basecamp 3 за 18 месяцев и покрыть веб для десктопа, веб для мобильных устройств, нативный iOS, нативный Android и email без гибрида и величественного монолита. Как минимум, не раздувая команду разработки. Это пять платформ и 200+ отдельных экранов.



Это напоминает мне ситуацию, которую я описал в статье Ruby has been fast enough for 13 years. Увеличение производительности означает не только то, что наши штуки становятся быстрее. Это также означает, что мы можем делать новые штуки, новыми способами. Способами, которые были до невозможности медленными раньше. Способами, которые заставляют плакать людей, умещавших полные компьютерные демо в 4 килобайта. Но эти способы, тем не менее, увеличивают общую продуктивность масс.



Это также напоминает мне историю, описанную Джоном Кармаком, легендарным программистом, создателем Doom и Quake. Когда он работал над игрой, ему нужно было писать код не для текущей на тот момент производительности, но на три года вперед, потому что игра выходила через три года. Если бы он программировал для сегодняшнего дня, то игра при выходе была бы уже устаревшей. Поэтому Doom и Quake всегда выглядели круто.



Подумайте об этом когда делаете приложение сегодня. Вы программируете для условий мира 2013 года? Или 2016? Или 2018? Программируйте там, где затык производительности будет, а не там, где он был.


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/311398/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Особенности программного ProxyChanging'а в Android. Часть 1: от Jelly Bean до Lollipop

Пятница, 30 Сентября 2016 г. 07:42 (ссылка)

Как-то раз, для собственного удобства, мне захотелось написать приложение, которое меняет настройки прокси в конфигурации сетей Wifi для Android. Задача, как мне тогда показалось, была на раз плюнуть, однако, на деле, как всегда, возникли непредвиденные сложности.







Если вы считаете полезным в будущем знать решение, хотите почерпнуть что-то для себя или в вас просто проснулось любопытство — добро пожаловать под кат. Там вас ждет внутреннее устройство классов отвечающих за конфигурацию Wifi в различных версиях Android, небольшая чашечка кода на Java и щепотка Reflection.



Немного общения с Google на тему «change wifi proxy settings in android programmatically» привели, разумеется, на StackOverflow, где присутствовало решение через Reflection. Недолго думая я скопировал код и запустил на своем девайсе.



Результат
image



Разочарование быстро сменилось интересом и азартом, примерно на этом моменте автор решил, что уже потратил слишком много времени, для того, чтобы просто забить на вышеуказанный вопрос, а так же понял, что данная задача требует более глобального подхода. Итак, поехали.



Шаг 1. Изучаем внутреннее устройство библиотеки android.net и отличия в Jelly Bean — Kitkat и Lollipop



Список вещей, которые желательно знать для лучшего понимания происходящего ниже

  • Your English level must be, at least, pre-intermediate.

  • У вас не должно возникать вопросов «Что такое Context?» и аналогичных по сложности, если же он возник на этом моменте — можете почитать developer.android.com или Александра Климова

  • Так же не должны вызывать смущения аннотации @Before, @Test, @After и прочие вещи относящиеся к тестированию. Опять же, ссылка: developer.android.com



Я бы с удовольствием разобрал все это, но сами понимаете — тогда моя статья вырастет в книгу.



Также я хотел бы дать еще несколько общих уточнений:




  • Вы почти не встретите комментариев в моем коде. Я долго размышлял над этим вопросом и очень долго сомневался, но, в конце концов, решил просто дать его девушке, которая вообще не знает java, и, после коротких пояснений, что такое class, void, throws, exception она смогла, прочтя несколько классов, весьма точно сказать, что происходит в них и их методах, потому я почти отказался от них.

  • Если у вас есть комментарии, дополнения, вопросы, замечания (например, по предыдущему пункту) — автор их очень ждет.

  • Статья на данный момент очень обобщена, опять же, если вы хотите увидеть некоторые моменты подробнее, то в зависимости от объема работ я напишу комментарий или, возможно, отдельную статью на заинтересовавшую вас тему.

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



Еще немного вопросов в Google привели меня на android.googlesource



Настройки прокси (а так же некоторые другие) заключены в экземпляре WifiConfiguration (Ссылка на класс для Kitkat mr2.2) для данной сети. При изучении данного класса был получен ответ на то, почему не работало на моем устройстве решение со StackOverflow. Оказалось, что начиная с пятой версии Android устройство класса WifiConfiguration, а так же пакета android.net претерпели значительные изменения и объекта LinkPropeties, с которым работал вышеуказанный код просто не существует в рамках данного класса. Зато присутствует объект IpConfiguraion с объектом ProxyInfo.



Учитывая что данные версии Android покрывали 80% различных устройств, то задача сводилась к тому, чтобы просто написать нечто такое:



public void changeProxySettings(String host, int port){
if(Build.VERSION.SDK_INT > 14 && Build.VERSION.SDK_INT < 20){
changeProxyWithLikProperties(String host, int port);
}else if(Build.VERSION.SDK_INT > 20 && Build.VERSION.SDK_INT < 23){
changeProxyWithProxyInfo(String host, int port);
}else{
throw new Exception("Sorry, android version not supported")
}
}


где changeProxyXXX — монструозные методы, на пару страниц. Не самое изящное решение.



Шаг 2. Разрабатываем библиотеку для настройки Wifi proxy в Android



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



Архитектура модуля


Мы имеем различные реализации под разные версии Android, у которых должен быть единый интерфейс для изменения настроек прокси, и работающие с объектом WifiConfiguration. Стараясь максимально удовлетворить данным требованиям, на начальном этапе мое воспаленное сознание придумало нечто такое:







Поясняющий комментарий к картинке выше

  • Класс BaseWifiConfiguration, по сути, хранит объект WifiConfiguration и содержит реализацию взятия конфигурации той сети, которая является текущей, при создании через Context.

  • Интерфейс ProxyChanger, соответственно, гарантирует наличие методов для работы с конфигурацией прокси сети.

  • Нам предстоит работа с Reflection, и желательно вынести основные методы для этого в отдельный класс, так как использоваться они будут часто. Поэтому создаем класс ReflectionHelper.



Классы для разных версий Android наследуются от BaseWifiConfiguration дабы иметь легкий доступ к экземпляру WifiConfiguration интересующей нас сети и облегчить работу с ним, и должны иметь реализации методов объявленных в ProxyChanger.



PS
Я не спорю, что это, быть может, не лучшее архитектурное решение и, если у Вас есть предложения по улучшению или какие-то замечания, — с радостью жду их в комментариях.



Например, меня очень интересует ReflectionHelper, как видите, он объявлен абстрактным, сделано это из соображений того, что он не должен иметь конкретных реализаций и используется только для структуризации и легкого доступа к интересующим нас методам. Я не знаю насколько правилен этот подход, так что если у вас есть комментарий по данному вопросу (или каким-то другим) — я буду премного благодарен его услышать.


Разумеется, это только общий каркас и, спустя несколько итераций рефакторинга, он обрастет новыми подробностями.



Тесты


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



Создаем небольшой класс, который будет отвечать за выбор ProxyChanger'а под конкретный api, класс для работы с вышеозначенным объектом в плане изменения конфигурации прокси и еще один, для взятия информации о настройках текущей сети, достаем пару телефонов и начинаем.



WifiProxyChangerTest.java
@RunWith(AndroidJUnit4.class)
public class WifiProxyChangerTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);

Context context;


@Before
public void prepare() throws Exception {
context = mActivityRule.getActivity();
ExceptionsPreparer.prepareExceptions(expectedException, context);
}

@Test
public void testChangeWifiStaticProxySettings() throws Exception {
String testIp = RandomValuesGenerator.randomIp();
int testPort = RandomValuesGenerator.randomPort();

WifiProxyChanger.changeWifiStaticProxySettings(testIp, testPort, context);

assertEquals(testIp, WifiProxyInfo.getHost(context));
assertEquals(testPort, WifiProxyInfo.getPort(context));
}

@Test
public void testProxySettingsClear() throws Exception {
String testIp = RandomValuesGenerator.randomIp();
int testPort = RandomValuesGenerator.randomPort();

WifiProxyChanger.changeWifiStaticProxySettings(testIp, testPort, context);
WifiProxyChanger.clearProxySettings(context);

assertEquals(ProxySettings.NONE, CurrentProxyChangerGetter
.chooseProxyChangerForCurrentApi(context)
.getProxySettings());
}

@After
public void сlearSettings() throws Exception {
if (NetworkHelper.isWifiConnected(context) && ApiChecker.isSupportedApi())
WifiProxyChanger.clearProxySettings(context);
}

}




WifiProxyInfoTest.java
@RunWith(AndroidJUnit4.class)
public class WifiProxyInfoTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);

Context context;


@Before
public void prepareAndPresetProxy() throws Exception {
context = mActivityRule.getActivity();

ExceptionsPreparer.prepareExceptions(expectedException, context);

if (ApiChecker.isSupportedApi()) {
WifiProxyChanger.clearProxySettings(context);
WifiProxyChanger.changeWifiStaticProxySettings("localhost", 3030, context);
}
}

@Test
public void testGetHost() throws Exception {
assertEquals("localhost", WifiProxyInfo.getHost(context));
}

@Test
public void testGetPort() throws Exception {
assertEquals(3030, WifiProxyInfo.getPort(context));
}

@Test
public void testGetProxySettings() throws Exception {
assertEquals(ProxySettings.STATIC, WifiProxyInfo.getProxySettings(context));
}

@After
public void сlearSettings() throws Exception {
if (NetworkHelper.isWifiConnected(context) && ApiChecker.isSupportedApi())
WifiProxyChanger.clearProxySettings(context);
}

}




CurrentProxyChangerGetterTest .java
@RunWith(AndroidJUnit4.class)
public class CurrentProxyChangerGetterTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);

Context context;


@Before
public void prepare() throws Exception {
context = mActivityRule.getActivity();
ExceptionsPreparer.prepareExceptions(expectedException, context);
}

@Test
public void testChooseProxyChangerForCurrentApi() throws Exception {
ProxyChanger proxyChanger = CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context);
WifiManager manager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

assertEquals(manager.getConnectionInfo().getNetworkId(), proxyChanger.getWifiConfiguration().networkId);

if (ApiChecker.isJellyBeanOrKitkat()) {
assertTrue(proxyChanger instanceof WifiConfigurationForApiFrom15To19);
} else if (ApiChecker.isLolipop()) {
assertTrue(proxyChanger instanceof WifiConfigurationForApiFrom21To22);
}
}

}




ExceptionsPreparer.java
public abstract class ExceptionsPreparer {

public static void prepareExceptions(ExpectedException expectedException, Context context) throws Exception {
if (!ApiChecker.isSupportedApi()) {
expectedException.expect(ApiNotSupportedException.class);
} else if (!NetworkHelper.isWifiConnected(context)) {
expectedException.expect(NullWifiConfigurationException.class);
} else if (!CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).isProxySetted()) {
expectedException.expect(WifiProxyNotSettedException.class);
}
}

}




Комментарий к коду, объясняющий, откуда там куча непонятных вещей про которые я не упомянул.
Я думаю, после прочтения возникли резонные вопросы: Что это за «ProxySettings.STATIC», и за что он отвечает, откуда взялись Exceptions, которые тоже ранее не упоминались, и так далее.

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



Запуск тестов оканчивается провалом, теперь нужно это как-то исправить.



Шаг 3. Реализация



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



Часть первая: Подготовительные работы и вспомогательные классы


Для начала — приступим к нашей обещанной щепотке Reflection


Чуть-чуть о Reflection api
Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine.

Oracle Java Turtorial


Класс, но что конкретно можно с этим сделать?



Небольшой пример
Пусть у нас есть маленькая библиотека, в ней пара классов для взятия веб страницы и ее сохранения. Я хочу подключить ее «на лету» и использовать. Напишем для этого небольшой класс:



public class LibLoader {
//Я искренне прошу прощения за этот код, писал за 2 минуты для примера.

URLClassLoader urlClassLoader;
String page;

LibLoader(File myJar) throws MalformedURLException {
urlClassLoader = new URLClassLoader(new URL[]{myJar.toURL()}, this.getClass().getClassLoader());
}

public void loadPage(URL url) throws Exception {
Class classToLoad = Class.forName("com.company.HtmlPageGetter", true, urlClassLoader);
Method method = classToLoad.getDeclaredMethod("getPageFromURL", URL.class);
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance, url);
page = (String) result;
}

public String getCurrentPage() {
return page;
}

public void saveCurrentPage(String name) throws Exception {
List content = new ArrayList<>();
content.add(page);
Class classToLoad = Class.forName("com.company.HtmlPageSaver", true, urlClassLoader);
Method method = classToLoad.getDeclaredMethod("savePageToFile", String.class, List.class);
Object instance = classToLoad.newInstance();
method.invoke(instance, name, content);
}
}


Теперь используем его:



    public static void main(String[] args) throws Exception {
File lib = new File("htmlgetandsave.jar");
LibLoader libLoader = new LibLoader(lib);
libLoader.loadPage(new URL("https://habrahabr.ru/post/69552/"));
System.out.println(libLoader.getCurrentPage());
libLoader.saveCurrentPage("Статья с хабра - Делаем reflection быстрой как прямые вызовы ");
}


Запускаем и наслаждаемся результатом:







Более того, мы могли вообще знать только расположение файла библиотеки и не знать ничего о ее структуре, Reflection api позволило бы изучить этот вопрос прямо в рантайме, и использовать ее после этого.



Однако для нас сейчас важно то, что помимо прочего, благодаря Reflection мы можем получить доступ к приватным полям и методам, а так же помеченным аннотацией hide.



Итак, пишем уже упомянутый выше ReflectionHelper.



ReflectionHelper.java
public abstract class ReflectionHelper {

/**
* Used for getting public fields with @hide annotation
*/
public static Object getField(Object object, String name)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field field = object.getClass().getField(name);
return field.get(object);
}

/**
* Used for getting private fields
*/
public static Object getDeclaredField(Object object, String name)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field declaredField = object.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField.get(object);
}

/**
* Used for setting private fields
*/
public static void setDeclaredField(Object object, String name, Object value)
throws NoSuchFieldException, IllegalAccessException {
Field declaredField = object.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

/**
* Used for setting Enum fields
*/
public static void setEnumField(Object object, String value, String name)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field field = object.getClass().getField(name);
field.set(object, Enum.valueOf((Class) field.getType(), value));
}

/**
* Used for simplifying process of invoking private method
* Automatically detects args types and founds method to get and invoke
*/
public static Object getMethodAndInvokeIt(Object object, String methodName, Object... args)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = object.getClass().getDeclaredMethod(methodName, parameterTypes(args));
method.setAccessible(true);
return method.invoke(object, args);
}

private static Class[] parameterTypes(Object... args) {
ArrayList classes = new ArrayList<>();
for (Object arg : args) {
classes.add(arg.getClass());
}
return classes.toArray(new Class[args.length]);
}

}


Здесь я все же поставил себе памятки, ибо, например, разницу между getField и getDeclaredField и в каких случаях какой использовать — можно легко забыть.



Большая часть работы с Reflection перенесена в отдельный класс, займемся реализацией остальных частей.



Создаем Exceptions на 3 случая:



  • Неподходящая версия Api. Соответствующий класс:



    public class ApiNotSupportedException extends Exception {

    public ApiNotSupportedException() {
    super("Api version not supported");
    }

    }



  • Попытка создания объекта с не заданной конфигурацией Wifi (Например, пользователь пытается с отключенным wifi изменить параметры прокси текущей сети):



    public class NullWifiConfigurationException extends Exception {

    public NullWifiConfigurationException(){
    super("WiFi configuration was null. \n" +
    "If you are trying to change current network settings - check your connection.");
    }

    }



  • Не определен объект для настроек прокси в текущем классе WifiConfiguration:



    public class WifiProxyNotSettedException extends IllegalStateException{

    public WifiProxyNotSettedException(){
    super("Wifi proxy not setted for current WifiConfiguration");
    }

    }





Реализуем класс служащий базовым для подклассов работающих с WifiConfiguration под различными api:


BaseWifiConfiguration.java
public class BaseWifiConfiguration {

protected WifiConfiguration wifiConfiguration;


protected BaseWifiConfiguration(WifiConfiguration wifiConfiguration)
throws NullWifiConfigurationException {
if (wifiConfiguration == null)
throw new NullWifiConfigurationException();
this.wifiConfiguration = wifiConfiguration;
}

protected BaseWifiConfiguration(Context context)
throws NullWifiConfigurationException {
this(getCurrentWifiConfigurationFromContext(context));
}

public WifiConfiguration getWifiConfiguration() {
return wifiConfiguration;
}

private static WifiConfiguration getCurrentWifiConfigurationFromContext(Context context) {
final WifiManager manager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
List wifiConfigurationList = manager.getConfiguredNetworks();
if (!manager.isWifiEnabled() || wifiConfigurationList == null || wifiConfigurationList.isEmpty())
return null;
return findWifiConfigurationByNetworkId(wifiConfigurationList, manager.getConnectionInfo().getNetworkId());

}

private static WifiConfiguration findWifiConfigurationByNetworkId(List wifiConfigurationList, int networkId) {
for (WifiConfiguration wifiConf : wifiConfigurationList) {
if (wifiConf.networkId == networkId)
return wifiConf;
}
return null;
}

}




Объявляем интерфейс ProxyChanger


ProxyChanger.java
public interface ProxyChanger {

void setProxySettings(ProxySettings proxySettings)
throws NoSuchFieldException, IllegalAccessException;

ProxySettings getProxySettings()
throws NoSuchFieldException, IllegalAccessException;

void setProxyHostAndPort(String host, int port)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException, NoSuchFieldException;

String getProxyHost()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException;

int getProxyPort()
throws ApiNotSupportedException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, NoSuchFieldException;

boolean isProxySetted()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException;

WifiConfiguration getWifiConfiguration();

}



Да, списки Exception'ов при использовании Reflection — это нечто.



Вроде все? А, нет, есть еще один маленький подпункт:



ProxySettings.java — что это вообще такое и зачем оно нужно?


Это аналог перечисления, находящегося в классе WifiConfiguration библиотеки Android. Мы создаем его за тем, чтобы облегчить работу с ним и не прописывать каждый раз STATIC, NONE и прочие вручную.



ProxySettings.java
public enum ProxySettings {

/* No proxy is to be used. Any existing proxy settings
* should be cleared. */
NONE("NONE"),
/* Use statically configured proxy. Configuration can be accessed
* with httpProxy. */
STATIC("STATIC"),
/* no proxy details are assigned, this is used to indicate
* that any existing proxy settings should be retained */
UNASSIGNED("UNASSIGNED"),
/* Use a Pac based proxy.
*/
PAC("PAC");


String value = "";


ProxySettings(String value) {
this.value = value;
}

public String getValue() {
return value;
}

}




Часть вторая: Пишем классы реализующие ProxyChanger под конкретные Api


Итак, настало время написать наконец тот самый код, который будет менять наши настройки прокси. Сразу оговорюсь, что есть разнообразные способы, чтобы добраться до них через Reflection: можно вызывать методы класса WifiConfiguration, можно добираться до, собственно, полей, где они находятся и через setDeclaredField менять их напрямую.



Я написал только часть для работы с текущей сетью (ибо это то, в чем нуждался автор), т.е. с созданием экземпляров классов через Context, однако, наша архитектура позволяет добавлением буквально нескольких строк адаптировать данные классы для работы с произвольным объектом WifiConfiguration.



Kitkat и Jelly Bean


Как уже говорилось в шаге 1, в данных версиях Android за хранение настроек Proxy отвечает объект ProxyProperties, хранящийся в LinkProperties, который в свою очередь находится в WifiConfiguration. Да, да, игла в яйце, яйцо в утке, утка в зайце и так далее.



Для того, чтобы изменить настройки прокси создадим новый экземпляр ProxyProperties с нужными нам параметрами, затем заменить данным объектом уже имеющийся и после этого настроить ProxySettings.



За создание экземпляров ProxyProperties будет отвечать отдельный класс:



ProxyPropertiesConstructor.java
public abstract class ProxyPropertiesConstructor {

public static Object proxyProperties(String host, int port)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException {
return proxyProperties(host, port, null);
}

public static Object proxyProperties(String host, int port, String exclList)
throws NoSuchMethodException, ClassNotFoundException, IllegalAccessException,
InvocationTargetException, InstantiationException {
return proxyPropertiesConstructor().newInstance(host, port, exclList);
}

private static Constructor proxyPropertiesConstructor()
throws ClassNotFoundException, NoSuchMethodException {
return Class.forName("android.net.ProxyProperties").getConstructor(String.class, int.class, String.class);
}

}




Для удобной работы с данным объектом также создадим класс-контейнер, содержащий объект ProxyProperties и предоставляющий доступ к основным полям (и позволяющий удобно создавать его сразу через host и порт):



ProxyPropertiesContainer.java
public class ProxyPropertiesContainer {

Object proxyProperties;


ProxyPropertiesContainer(Object proxyProperties) {
this.proxyProperties = proxyProperties;
}

ProxyPropertiesContainer(String host, int port)
throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
this(host, port, null);
}

ProxyPropertiesContainer(String host, int port, String exclList)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException {
this(ProxyPropertiesConstructor.proxyProperties(host, port, exclList));
}

public String getHost()
throws NoSuchFieldException, IllegalAccessException {
return (String) ReflectionHelper.getDeclaredField(proxyProperties, "mHost");
}

public int getPort()
throws NoSuchFieldException, IllegalAccessException {
return (int) ReflectionHelper.getDeclaredField(proxyProperties, "mPort");
}

public String getExclusionList()
throws NoSuchFieldException, IllegalAccessException {
return (String) ReflectionHelper.getDeclaredField(proxyProperties, "mExclusionList");
}

public Object getProxyProperties() {
return proxyProperties;
}

}




Теперь пишем реализацию собственно класса:



WifiConfigurationForApiFrom15To19.java
public class WifiConfigurationForApiFrom15To19 extends BaseWifiConfiguration implements ProxyChanger {

private ProxyPropertiesContainer proxyPropertiesContainer;


public WifiConfigurationForApiFrom15To19(Context context)
throws NoSuchFieldException, IllegalAccessException, NullWifiConfigurationException {
super(context);
this.proxyPropertiesContainer = new ProxyPropertiesContainer(getCurrentProxyProperties());
}

public static WifiConfigurationForApiFrom15To19 createFromCurrentContext(Context context)
throws NoSuchFieldException, IllegalAccessException, NullWifiConfigurationException {
return new WifiConfigurationForApiFrom15To19(context);
}

@Override
public void setProxySettings(ProxySettings proxySettings)
throws NoSuchFieldException, IllegalAccessException {
ReflectionHelper.setEnumField(wifiConfiguration, proxySettings.getValue(), "proxySettings");
}

@Override
public ProxySettings getProxySettings()
throws NoSuchFieldException, IllegalAccessException {
return ProxySettings.valueOf(String.valueOf(ReflectionHelper.getDeclaredField(wifiConfiguration, "proxySettings")));
}

@Override
public void setProxyHostAndPort(String host, int port)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException, NoSuchFieldException {
proxyPropertiesContainer = new ProxyPropertiesContainer(host, port);
ReflectionHelper.getMethodAndInvokeIt(
getLinkProperties(),
"setHttpProxy",
proxyPropertiesContainer.getProxyProperties());
}

@Override
public String getProxyHost()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException {
if (proxyPropertiesContainer == null)
throw new WifiProxyNotSettedException();
return proxyPropertiesContainer.getHost();
}

@Override
public int getProxyPort()
throws ApiNotSupportedException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, NoSuchFieldException {
if (proxyPropertiesContainer == null)
throw new WifiProxyNotSettedException();
return proxyPropertiesContainer.getPort();
}

@Override
public boolean isProxySetted()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException {
return !(proxyPropertiesContainer == null);
}

private LinkProperties getLinkProperties()
throws NoSuchFieldException, IllegalAccessException {
return (LinkProperties) ReflectionHelper.getField(wifiConfiguration, "linkProperties");
}

private Object getCurrentProxyProperties()
throws NoSuchFieldException, IllegalAccessException {
return ReflectionHelper.getDeclaredField(getLinkProperties(), "mHttpProxy");
}

}




C этой версией закончили, остался:



Lollipop


Опять же, апеллируя к шагу 1, можно сделать вывод, что настройки прокси в данной версии Api находятся в классе ProxyInfo, содержащемся в IpConfiguration, который в свою очередь имеет своим местом дислокации наш WifiConfiguration. ProxySettings — тоже переехал, теперь он в вышеупомянутом IpConfiguration.



Напишем класс, делающий новые экземпляры ProxyInfo по заданным параметрам.



ProxyInfoConstructor.java
public abstract class ProxyInfoConstructor {

public static ProxyInfo proxyInfo(String host, int port)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
return proxyInfo(host, port, null);
}

public static ProxyInfo proxyInfo(String host, int port, String exclude)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
Object newProxyInfo = proxyInfoConstructor().newInstance(host, port, exclude);
return (ProxyInfo) newProxyInfo;

}

private static Constructor proxyInfoConstructor()
throws ClassNotFoundException, NoSuchMethodException {
return Class.forName("android.net.ProxyInfo").getConstructor(String.class, int.class, String.class);
}

}




Как видите, здесь мы уже возвращаем не Object'ы, а именно экземпляры ProxyInfo, более того, далее будет видно, что у этого класса есть еще и методы getHost и getPort. В предыдущем случае мы этого сделать не могли, класс ProxyProperties был спрятан, именно поэтому мы писали для него «оболочку».



И, собственно, код для еще одной реализации:



WifiConfigurationForApiFrom21To22.java
public class WifiConfigurationForApiFrom21To22 extends BaseWifiConfiguration implements ProxyChanger {

public WifiConfigurationForApiFrom21To22(Context context)
throws NullWifiConfigurationException {
super(context);
}

public static WifiConfigurationForApiFrom21To22 createFromCurrentContext(Context context)
throws NullWifiConfigurationException {
return new WifiConfigurationForApiFrom21To22(context);
}

@Override
public ProxySettings getProxySettings()
throws NoSuchFieldException, IllegalAccessException {
return ProxySettings.valueOf(String.valueOf(ReflectionHelper.getDeclaredField(getIpConfigurationObject(), "proxySettings")));
}

@Override
public void setProxySettings(ProxySettings proxySettings)
throws NoSuchFieldException, IllegalAccessException {
ReflectionHelper.setEnumField(getIpConfigurationObject(), proxySettings.getValue(), "proxySettings");
}

@Override
public void setProxyHostAndPort(String host, int port)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException, NoSuchFieldException {
setProxyInfo(ProxyInfoConstructor.proxyInfo(host, port));
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public String getProxyHost()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException {
ProxyInfo info = getProxyInfo();
if (info == null)
throw new WifiProxyNotSettedException();
return info.getHost();
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public int getProxyPort()
throws ApiNotSupportedException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
ProxyInfo info = getProxyInfo();
if (info == null)
throw new WifiProxyNotSettedException();
return info.getPort();
}

@Override
public boolean isProxySetted()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException {
return !(getProxyInfo() == null);
}

private Object getIpConfigurationObject()
throws NoSuchFieldException, IllegalAccessException {
return ReflectionHelper.getDeclaredField(wifiConfiguration, "mIpConfiguration");
}

private ProxyInfo getProxyInfo()
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return (ProxyInfo) ReflectionHelper.getMethodAndInvokeIt(wifiConfiguration, "getHttpProxy");
}

private void setProxyInfo(ProxyInfo proxyInfo)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
NoSuchFieldException {
ReflectionHelper.getMethodAndInvokeIt(wifiConfiguration, "setHttpProxy", proxyInfo);
}

}




С основной реализацией на этом все. До финиша осталось совсем чуть-чуть.



Шаг 4. Предстартовая подготовка



Реализуем классы, упоминавшиеся ранее в тестах (замечание: мы реализуем настройку прокси по IP и порту, соответственно тип ProxySettings STATIC.)



CurrentProxyChangerGetter.java
public abstract class CurrentProxyChangerGetter {

public static ProxyChanger chooseProxyChangerForCurrentApi(Context context)
throws ApiNotSupportedException, NoSuchFieldException, IllegalAccessException,
NullWifiConfigurationException {
if (ApiChecker.isJellyBeanOrKitkat()) {
return WifiConfigurationForApiFrom15To19.createFromCurrentContext(context);
} else if (ApiChecker.isLolipop()) {
return WifiConfigurationForApiFrom21To22.createFromCurrentContext(context);
} else {
throw new ApiNotSupportedException();
}
}

}




WifiProxyChanger.java
public abstract class WifiProxyChanger {

public static void changeWifiStaticProxySettings(String host, int port, Context context)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException, NoSuchFieldException,
ApiNotSupportedException, NullWifiConfigurationException {
updateWifiWithNewConfiguration(
getCurrentWifiConfiguretionWithUpdatedSettings(host, port, ProxySettings.STATIC, context),
context);
}

public static void clearProxySettings(Context context)
throws IllegalAccessException, ApiNotSupportedException, NoSuchFieldException,
NullWifiConfigurationException, ClassNotFoundException, NoSuchMethodException,
InstantiationException, InvocationTargetException {
updateWifiWithNewConfiguration(
getCurrentWifiConfiguretionWithUpdatedSettings("", 0, ProxySettings.NONE, context),
context);
}

private static WifiConfiguration getCurrentWifiConfiguretionWithUpdatedSettings(String host, int port, ProxySettings proxySettings, Context context)
throws ApiNotSupportedException, IllegalAccessException, NullWifiConfigurationException,
NoSuchFieldException, ClassNotFoundException, NoSuchMethodException,
InstantiationException, InvocationTargetException {
ProxyChanger proxyChanger = CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context);
proxyChanger.setProxyHostAndPort(host, port);
proxyChanger.setProxySettings(proxySettings);
return proxyChanger.getWifiConfiguration();
}


private static void updateWifiWithNewConfiguration(WifiConfiguration wifiConfiguration, Context context) {
WifiManager currentWifiManager = NetworkHelper.getWifiManager(context);
currentWifiManager.updateNetwork(wifiConfiguration);
currentWifiManager.saveConfiguration();
currentWifiManager.reconnect();
}

}




WifiProxyInfo.java
public abstract class WifiProxyInfo {

public static String getHost(Context context)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException, NullWifiConfigurationException {
return CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).getProxyHost();
}

public static int getPort(Context context)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
ApiNotSupportedException, NoSuchFieldException, NullWifiConfigurationException {
return CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).getProxyPort();
}

public static ProxySettings getProxySettings(Context context)
throws ApiNotSupportedException, IllegalAccessException, NoSuchFieldException,
NullWifiConfigurationException {
return CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).getProxySettings();
}

}




Реализуем вспомогательный класс для проверки версии API:



ApiChecker.java
public abstract class ApiChecker {

public static boolean isJellyBeanOrKitkat() {
return Build.VERSION.SDK_INT > 14 && Build.VERSION.SDK_INT < 20;
}

public static boolean isLolipop() {
return Build.VERSION.SDK_INT > 20 && Build.VERSION.SDK_INT < 23;
}

public static boolean isSupportedApi() {
return isJellyBeanOrKitkat() || isLolipop();
}

}




ЗАПУСКАЕМ ТЕСТЫ



(прошу прощения, но это такой момент, что я решился выделить его заголовком)









Шампанское! Вино! Народные гулянья! Аплодисменты! Queen — We are the champions в качестве музыкального сопровождения!



Небольшой комментарий
Разумеется, автор запускал тесты до этого момента несколько десятков раз и созерцал ошибки в огромных количествах. А иногда ему приходилось править и криво написанные тестовые сценарии, в которых была допущена какая-нибудь пустяковая ошибка, типа пропущенного "!". Но, так как к моменту написания статьи код уже был по 10 раз отрефакторен, переотрефакторен и выотрефакторен, то почему бы не позволить себе немного приукрасить действительность.


Шаг 5. Наслаждаемся плодами нашей деятельности



Подключаем библиотеку к приложению созданному по умолчанию и проверяем результат:



MainActivity.java
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
changeProxySettings("myhost.com", 12345);
}

void changeProxySettings(String host, int port) {
try {
WifiProxyChanger.changeWifiStaticProxySettings(host, port, this);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | NullWifiConfigurationException | ApiNotSupportedException e) {
e.printStackTrace();
}
}

}




Запускаем. На экране приложения мы ничего интересного не увидим, потому сразу идем в настройки wifi сети к которой мы подключены.



Результат. Картинка великовата.




Результат достигнут.



Подведение итогов



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



Дальнейшие планы? Да у автора их целый список!




  • Разумеется, нужно добавить поддержку Marshmallow, как раз появился новый телефон под управлением данной версии Android (и чувствую, что работа с новой системой разрешений будет той еще задачей).

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

  • Возможно, стоит доработать библиотеку для полноценного изменения конфигурации Wifi сетей. WifiConfiguration — класс большой и интересный, и, быть может, скоро в библиотеке появится интерфейс IpSettingsChanger, а с ним — и новая статья.

  • И, разумеется, нужно нормально оформить readme и прочие вещи на Bitbucket.



И, разумеется это далеко не все.



Post scriptum и еще немного комментариев автора
» Если вы заинтересовались ссылкой на библиотеку, дабы посмотреть, что она являет собой в текущем состоянии, то по запросу в комментариях я прикреплю ссылку на Bitbucket (либо оставлю в комментах).



» Если вас интересует какие либо подробности — автору можно свободно писать, он всегда рад общению с умными людьми.



» Если вы внезапно осознали, что вашему проекту будет полезен функционал реализованный или близкий к данному, и вам захочется использовать сии наработки — пожалуйста.


Спасибо за интерес к статье, искренне ваш, «Nickname, который нельзя оставить в песочнице»
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/311388/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Google Tango: управляем роботом в режиме дополненной реальности

Четверг, 29 Сентября 2016 г. 21:29 (ссылка)

Благодаря Хабрахабру и Google к нам в лабораторию робототехники Сколтеха на неделю попал дев-кит планшет проекта Google Tango. Мы не только протестировали данное устройство, но и написали небольшое демо по управлению мобильным роботом в режиме дополненной реальности. Но об этом чуть позже, сначала немного об устройстве и наших впечатлениях от него.



Статья автора Дмитрия Сенашенко, в рамках конкурса «Device Lab от Google».




Думаю многие из вас уже слышали о данном проекте и неплохо представляют что он из себя представляет. Если вкратце, то это платформа компьютерного зрения и локализации нацеленная на применение в мобильных устройствах. Используя данные с двух камер (широкоугольной и обычной), датчика глубины (по сути Kinect в миниатюре), акселерометров, гироскопов и барометра устройство проекта Google Tango способно воспринимать окружающее трёхмерное пространство и отслеживать своё положение в нём. Громадная заслуга группы инженеров ATAP (Advanced Technology and Projects) заключается не только в том, что они смогли уместить всё это оборудование в мобильном устройстве, но и в том что у них вышло разработать дружелюбное к разработчику высокоуровневое SDK, которое берёт на себя основную тяжёлую работу по обработке данных с сенсоров и проведению необходимых преобразований, позволяя разработчику работать с удобными абстракциями. Так же в лучших традициях Google нам доступна документация высокого качества, позволяющая достаточно быстро освоиться с устройством даже разработчикам без опыта разработки приложений под Android.



Об устройстве



Принцип работы







Устройство по сути имеет два основных режима локализации: с Area Learning и без него. В первом режиме мы предварительно сканируем помещение и строим его карту (к сожалению это делается offline, т.е. сначала обработка накопленных данных, потом использование результата в виде файла ADF — Area Description File), после чего мы можем весьма точно локализоваться в изученном помещении, компенсировать дрейф и справляться с проблемой временной потери трекинга. (например, при закрытии сенсоров рукой или другим слишком близко поднесённым объектом)



Второй режим позволяет нам проводить локализацию в пространстве и отслеживание движение устройства безо всякой предварительной подготовки. Работает он на основе совмещения данных со всех датчиков: IMU (Inertial Measurment Unit), визуальной одометрии по особым точкам изображения широкоугольной камеры, датчика глубины и т.д. Но т.к. нам неизвестны точки за которые мы могли бы зацепиться, в данном режиме координаты устройства будут подвержены дрейфу за счёт постоянно накапливающейся ошибки. (см. иллюстрацию) Кроме того имеется риск потери трекинга, корректное восстановление из которого в данном режиме в общем случае невозможно.



Пользуясь данными локализации (т.е. по сути зная с некоторой точностью координаты и ориентацию устройства относительно помещения) и имея трёхмерное облако точек с датчика глубины мы имеем возможность создавать приложения дополненной реальности ранее принципиально невозможные на мобильных устройствах. Логичным продолжением была бы установка Tango на очки дополненной реальности (следующая итерация Google Glass наподобии Hololens?), но пока мы можем воспользоваться эрзац-заменителем в виде Google Cardboard.



Немного о точности



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

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




  • Среднее отклонение составило 2-3 см, в худших случаях вплоть до 5-6 см


  • Точность с Area Learning и без него на траекториях 15-20 метров кардинально не отличаются, что говорит о достаточно высоком качестве локализации по визуальной одометрии и IMU


  • Ориентация устройства влияет на координаты с ошибкой вплоть до 5 см (в т.ч. и с использованием Area Learning), т.е. если вернуть устройство в исходную точку, но повёрнутым, его координаты будут несколько иными




По понятным причинам точность сильно зависит от помещения в котором проводятся измерения. В помещении с хорошим освещением (устройство плохо относится к прямому солнечному свету) и большим количеством особых точек вполне реально добиться точности в пару сантиметров. Но в плохих условиях устройство быстро теряет трекинг, IMU конечно до поры до времени выручает, но его возможности весьма ограничены. Поэтому мы и бросили идею поставить танго на индустриальную робо-руку и измерить таким образом точность, ибо комната с роботом плохой пример «обычного» помещения.



Теперь о точности датчика глубины. Проверять его точность мы решили снимая облака точек для плоских объектов (пола, стен, столов) и анализируя насколько хорошо точки ложатся на плоскость. На оптимальной дистанции 0.5-4 м точность обычно составляла около 0.5 см, но на некоторых поверхностях точность падала в 2-3 раза, например на поле нашей лаборатории, покрытом черно-белым ковром в крапинку. Похоже текстура играла злую шутку с алгоритмами определения глубины основанными на структурированном ИК излучении.



Об SDK и API



Если кратко, то Google на высоте. Думаю как только Tango устройства попадут в широкую продажу, то отбоя от разработчиков не будет не только из-за уникальных возможностей устройства, но и из-за простоты программирования приложений для него. Фраза во вступлении, о том что с девайсом может освоиться даже разработчик без опыта программирования под Android — эксперементально подтверждённый факт, т.к. основной разработчик для Tango нашего демо управления роботом — Марко Симик (иностранный магистр нашей лаборатории), практически не имел опыта разработки под Android, но тем не менее смог за пару дней смог изучить инструменты и API в объёме достаточном для написания простеньких приложений.







Но хватит похвал. Tango Service представляет собой сервис работающий отдельным процессом. Общая структура программного стека показана на иллюстрации.



SDK предоставляет возможность работать с C++, Java и Unity. Порядок примерно соответствует их «высокоуровневости». Разумеется разработчики игр скорее всего оценят по достоинству возможность использования Unity и будут преимущественно выбирать данный вариант. Если же вы хотите работать напрямую с AIDL (Android Interface Definition Language) или другими Java приложениями, то Java API для вас. Разработчики же желающие разрабатывать приложения с Android NDK и иметь более полный контроль выберут C API.



Во всех трёх вариантах API практически идентично и предоставляет инструменты для съёма данных с устройства, управлением им и проведения необходимых преобразований из различных систем координат. (коих имеется аж 6 штук)



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



Ложка дёгтя



Однако, с дев-китом ситуация обстояла далеко не идеально. Временами Tango Service падал и либо отказывался работать, либо выдавал шум на выходе. В подобных случаях помогала только перезагрузка. Вообще чувствовалась некоторая сырость устройства, будем надеяться что это свойство дев-кита и в коммерчески доступных устройствах эти детские болячки будут исправлены.



Кроме того стоит отметить что устройство достаточно ощутимо нагревается, особенно в приложениях с 3D гафикой. Тут, к сожалению, принципиально поменять ситуацию вряд ли получится, т.к. тяжёлые вычисления производить надо в любом случае. Так что на продолжительную работу Tango устройств при активном использовании, думаю, рассчитывать не приходится. Для оценки можете взять свой телефон или планшет и запустить на нём 3D игру, примерное время жизни устройства думаю будет плюс-минус совпадать.



Кроме того не стоит ожидать чудес от построения карт. Примерно качество можно увидеть например в этом видео.







Плюс стоит помнить, что устройство не является волшебной палочкой и у него имеются собственные ограничения, которые весьма вероятно послужат сдерживающим фактором при его консьюмерском использовании, ибо обычный пользователь не подозревает об особенностях используемых алгоритмов локализации, поэтому во время игры на Tango устройстве он может сталкиваться с весьма неприятными лагами, прыжками и прочими проявлениями неидеальности работы. Например, мы так и не смогли сыграть в игры поставлявшиеся с устройством так, что бы уверено сказать, что они готовы для обычных пользователей, скорее всего текущие игры они опишут просто как «глючные и непонятные». Но возможно это просто симптом сырости устройства.



Демо: управляем роботом



Видео с конечным результатом недельного знакомства с Google Tango:







Исходный код скриптов для Unity опубликован на Гитхабе.



Идея



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



В нашем случае мы решили сделать виртуальный интерфейс для вполне реального мобильного робота. Т.к. мы были весьма ограничены по времени, то решили ограничиться лишь управлением роботом посредством задания точек куда ехать роботу в реальном пространстве. Разумеется данную идею можно продолжать множеством направлений: добавлять дополнительные интерфейсы для робота (например, «голограмму» человека над роботом, визуализировать обмен данными между роботами), создавать обратную связь (например, показывая запланированную траекторию робота), делать игры совмещающие реальное и виртуальное (например, собирать виртуальные объекты реальной машинкой) и тому подобное.



Но даже простое исполнении контроля робота через задание целевых точек может оказаться весьма полезной фичей, особенно на производствах в которых становится всё больше и больше роботов.

Не говоря уже о потенциальных возможностях развития идей использования Google Tango для контроля физических объектов.



Исполнение



Для реализации данной идеи мы решили использовать Unity API, как наиболее простое и лёгкое для построения демо приложения в виду своей высокоуровневости. Для повышения надёжности определения координат мы использовали локализацию с использованием Area Learning. (на практике, вероятнее всего, роботы будут использоваться в известных помещениях, промерить которые не составит труда). Конечно можно было обойтись и без него, но точность и надёжность значительно от этого пострадают.



Разумеется, что бы приложение заработало желательно, что бы робот имел собственные средства навигации в пространстве, иначе нам постоянно придётся держать робота в области видимости устройства, что согласитесь не очень удобно. В нашем мобильном роботе использовался двухмерный лазерный сканер Hokuyo-04LX и программное обеспечение реализующие SLAM (одновременная локализация и картографирование), на выходе которого мы получали карту занятости (occupancy grid) окружающей местности, используя которую мы уже можем планировать траекторию движения робота. (софт для робота был по большей части самописным, но всё то же самое можно сделать используя готовые модули в ROS).



Теперь нам остаётся только сопоставить глобальные координаты устройства и робота, причём первые координаты уже выровнены по горизонтали посредством использования акселерометра, так что нам нужно по сути локализовать робота и его ориентацию в глобальном двухмерном пространстве устройства, после чего мы сможем проводить преобразования координат целевых точек из системы планшета в систему робота.



Для того что бы реализовать данную задумку приложению необходимо было включить в себя три фичи: трекинг движения, Area Learning и получение карты глубин. (или иначе говоря трёхмерного облака точек) Использование приложения проходит по следующему пути:




  1. Записать Area Description File (ADF), т.е. провести Area Learning помещения в котором будет использоваться программа


  2. Загрузить полученный ADF, подвигать немного устройство дабы устройство распознало особые точки и произвело собственную локализацию


  3. Отметить положение робота


  4. Отметить целевую точку и нажать кнопку исполнения команды




Причём стоит отметить, что отмечая положение робота мы узнаём только его координату, но не направление, тут мы пошли на ухищрение и упростили задачу создав правило, что робота надо всегда выбирать стоя строго сзади него. Конечно в общем случае робота можно детектировать по специальным меткам (скажем отображающимся на экране или наклеенным на корпусе) или же вообще используя нейронные сети на основе TensorFlow. Но опят же сказались ограничения по времени.



Код



Исходники в репозитории (ссылка выше) содержат лишь три скрипта: выбор файла ADF, сплеш-скрин инициализации (релокализации) после выбора ADF, основной скрипт управления и UI. Что бы воспользоваться этими скриптами достаточно добавить их в пример AreaLearning.



Unity скрипты исполняются определённым образом, имеется 3 главных коллбека, которые мы используем в нашем демо:




  • Start() запускается при запуске приложения


  • Update() запускается при каждом обновлении кадра


  • OnGUI() запускается несколько раз за кадр




В итоге у нас получилась следующая структура:




  • Start() назначает коллбеки к соответвующим событиям


  • Update() обрабатывает касания к экрану и при необходимости запускает корутину для нахождения глобальных координат соответствующих тапнутым координатам экрана, плюс рисует красный маркер при успешном выборе точки


  • OnTangoDepthAvailable, OnTangoPoseAvailable коллбеки ожидающие событий от Tango и устанавливающих соответствующие флаги при запуске


  • _WaitForTouch корутины ожидающие тапа по экрану после нажатия одной из кнопок, после чего вызывает корутину для вычисления глобальных координат соответствующих месту тапа


  • _WaitForDepth ожидает карту глубин (трёхмерное облако точек) и находит координаты точки в глобальной системе отсчёта для заданной координаты на экране


  • _WaitForRequest обрабатывает посылку команды роботу, в нашем случае это был простой GET запрос




Код управления робота, думаем, выходит за рамки данной статьи, поэтому на данный момент не публикуется.



Заключение



По итогам нашего знакомства могу с уверенностью сказать: Google Tango чрезвычайно многообещающее устройство, которое способно совершить в ближайшие года переворот в том, что мы считаем мобильными устройствами. В общем, весьма маловероятно что данный проект закончит как Glass и Wave. Разумеется на данный момент устройства не лишены детских болячек, и как любая технология имеют свои пределы и особенности, но первое будет поправлено, а ко второму, думаю, пользователи и разработчики постепенно привыкнут.



Таким образом, мы считаем, что давно навзревавший бум виртуальной реальности по видимому скоро раскроется в полную силу, и Google Tango явно намерен возглавить его посредством своей простоты программирования, дружелюбия к разработчикам, сложившейся Android экосистемы, в так же активного продвижения Google.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/309870/

Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<android - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda