Posts tagged howto

Android MapView a onTouch

0

Nemálo mě, a nejenom mě, štve absence něčeho, jako je onTouch() v mapách na Androidu, protože potřebuju vytvořit dynamické načítání overlayů. MapView sice má onTouchEvent(), ale že by bylo možné jej použít nějakým mnou zamýšleným způsobem, to zrovna ne. Zkoušel jsem pár způsobů, které tu nastíním a taky jsem zkusil jedno ne zrovna dvakrát moc fér řešení. Ale zoufalí lidé se uchylují k zoufalým věcem, však to znáte… Jen to vezmu hodně hopem, protože mám moře jiných věcí na dělání, ale pár lidem jsem to slíbil a sliby se mají plnit a to nejen o Vánocích ;-).

Zatímco natahování je poměrně jasné (AsyncTask, který bude natahovat data ve čtverci, jehož směrem jsem se posunul – na základě kolize souřadnic tohoto čtverce a viewportu – aby to pořád netahalo jak blbé), taková trivialita jako je onTouch už tak jasná není.

Nejprve jsem se snažil přimět k rozumu metodu onTouchEvent() jak na MapView, tak i na Overlayi, bohužel bezúspěšně. Zajímalo by mě, jestli je tento event pozůstatek něčeho zaniklého, nebo se na něj jenom nějak zapomnělo. Tento způsob by měl fungovat nějak takto, což vypadá poměrně prakticky a použitelně – ve switchi checkovat eventy:

public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            . . .
        }
    return super.onTouchEvent(ev);
}

Bohužel – z nějakého důvodu tato metoda vůbec nebyla volána. Po nějakém googlení jsem narazil na blog Juriho Strumpflohnera, který řešil stejný problém a vyšpekuloval ho následovně: na mapView navěsit vlastní OnTouchListener(), čili přidat jednoduchou anonymní třídu:

mapView.setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent ev) {
        return false;
    }
});

Pokud teda dojde k eventu Touch, jsou procházeny všechny views a případné implementace listenerů v nich. V listeneru implementovaná metoda onTouch je volána nejméně jednou v závislosti na tom, zda vrátí true či false. Pokud vrací false, provede se pouze poprvé a systém je tak chytrý, že už jde příště rovnou na jistotu (aka přímo po metodě, která vrátila true), což je zároveň i kámen úrazu: je nutné zajistit, aby byla volána i metoda „níže“, protože se při true mapa nepohybuje (ale náš onTouch je volaný vždycky). Je teda nutné předat „nižšímu view“ tento event. Nejprve jsem volal dispatchTouchEvent() na view, který přišel do onTouch, ale to je blbost, protože tento view je vlastně onen MapEvent, nad kterým byl navěšen tento listener, takže došlo k zacyklení a StackOverflowError. Takže jsem třídu odanonymizoval a předávám si do ní Context, což je aktivita, uvnitř které je mapView:

public class MyTouchListener implements View.OnTouchListener {
    private Activity activity;

    public MyTouchListener(Activity activity) {
        super();
        this.activity = activity;
    }

    public boolean onTouch(View v, MotionEvent ev) {
        activity.dispatchTouchEvent(ev);
        return true;
    }
}

Taky to ovšem nejede, ale oproti dispatchování do view to vydrží o chvíli déle (a pak to stejně přeteče zásobník a zdechne).

Jak už to bývá, funkční řešení bývají ta nejjednodušší, takže dneska tenhle blogpost konečně dorazím!

Ono totiž stačí rozšířit map view a přepsat v něm onTouchEvent, nebo onInterceptTouchEvent (jak je popsáno tady). V čem je zakopán pudl? V ničem, opravdu to funguje. Předchozí Class Cast Exception se dá zbavit velice jednoduše – definovat správný typ v layoutu! Pojďme tedy na to, nejprve vlastní mapView:

package cz.shmoula.pat;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import com.google.android.maps.MapView;

public class AreaMapView extends MapView {
	private Context mContext;

	public AreaMapView(Context context, AttributeSet attributeSet) {
		super(context, attributeSet);
		mContext = context;
	}

	public boolean onTouchEvent(MotionEvent ev) {
		if (ev.getAction() == MotionEvent.ACTION_MOVE) {
			Log.i("!!!!!", "Pohyb!");
		}
		return super.onTouchEvent(ev);
	}
}

Teď následuje onen výše zmíněný kámen úrazu, který dříve hulákal Class Cast výjimkou a je to docela logické, ale nevěděl jsem, že je možné vkládat do layoutů vlastní komponenty. Teď už to vím, tak je možné psát něco zhruba takového:

<?xml version="1.0" encoding="utf-8"?>

<cz.shmoula.pat.AreaMapView
    xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/areaMapView"

    android:layout_width="fill_parent"
    android:layout_height="fill_parent"

    android:clickable="true"
    android:apiKey="api_klíč"
/>

No a v aktivitě se tato komponenta dohledá každému známým způsobem:

AreaMapView mapView = (AreaMapView) findViewById(R.id.areaMapView);

Toť vše! Opravdu, v jednoduchosti je síla. Takže teď přidat nějaké spočítání delta a pokud bylo pohnuto viewportem více, než je výše uvedený čtverec v onu stranu, tak načíst další. Na závěr se ještě musím přiznat, že toto řešení jsem nevyplodil sám, ale sprostě se nechal inspirovat OpenCachingem od Garmina, který mě nakopl na správnou cestu směrem k výsledku. Při pídění se po řešení jsem taky narazil na projekt mapview-overlay-manager, ve kterém se nachází Lazy loading a který si nechávám k dalšímu prostudování a hádám, že nebudu sám.

Dekompilace androidích aplikací

0

Někdo se rád pídí po tom, jak věci fungují, někdo se rád občas nechává inspirovat cizími kusy kódu a někdo prachsprostě krade (možná by se to dalo nazvat nějakou IT kleptomanií). Všem těmto skupinám by ovšem mohl přijít vhod tento blogpost, protože v několika krocích popisuje operaci jednoduchou (tu jednoduchost myslím vážně, zabývat se dedexerem, aka android disassemblerem opravdu nehodlám, z toho už jsem vyrostl a zpohodlněl) – a sice rozbalování a dekompilaci androidí aplikace.

Jak je tvořena androidí aplikace asi každý vývojář ví, takže tuto část vypustíme a přejděme blíže k věci, což by mohl být aapt, neboli Android Asset Packing Tool, který je součástí nástrojů v SDK. Tento nástroj je jednak zodpovědný za parsování všemožných souborů (včetně manifestu) uvnitř projektu a následné přetváření takových properties do R.java souboru, ale především i za proces opačný, čili dolování informací z již hotových .apk souborů a to i podepsaných a zarovnaných. Nebudeme chodit kolem horké kaše a skočme rovnou do nějakého příkladu. Půjčil jsem si APKčko z mého rozdělaného projektu a provedl nad ním následující kouzlo:

vbalak@vbalak-desktop:~/develop/workspace.sts/PlantATree/client/target$ aapt l -a pat-client.apk
res/layout/area_map.xml
res/layout/main.xml
res/layout/tree_details.xml
res/layout/tree_list.xml
res/layout/tree_list_item.xml
res/menu/map_menu.xml
AndroidManifest.xml
resources.arsc
res/drawable-hdpi/icon.png
res/drawable-ldpi/icon.png
res/drawable-mdpi/icon.png
classes.dex
org/codehaus/jackson/map/VERSION.txt
org/codehaus/jackson/impl/VERSION.txt
META-INF/MANIFEST.MF
META-INF/CERT.SF
META-INF/CERT.RSA

Nejprve je vidět seznam souborů nacházejících se v archivu (.apk je obyčejný zip soubor, takže je možné jej jednoduše rozbalit, v tom není žádná věda). Další část (resource table) je zajímavější – jedná se o identifikátory zdrojů, neboli to, co je v souboru R.java – to jsou ty hexa čísla uvedená bezprostředně za spec resource a dále typ tohoto zdroje – layout, string, drawable, id…

Resource table:
Package Groups (1)
Package Group 0 id=127 packageCount=1 name=cz.shmoula.pat
  Package 0 id=127 name=cz.shmoula.pat typeCount=6
    type 0 configCount=0 entryCount=0
    type 1 configCount=3 entryCount=1
      spec resource 0x7f020000 cz.shmoula.pat:drawable/icon: flags=0x00000100
      spec resource 0x7f04000a cz.shmoula.pat:string/menu_toggle_view: flags=0x00000000
. . .

Nejzajímavější je ovšem část poslední, což je sám velký manifest. Kdo měl možnost nahlédnout do AndroidManifest.xml a teď kouká na tento výpis, všímá si velké podobnosti (pokud potlačí rozdílný způsob zobrazení – tohle není xml ;-)). Jsou zde krásně vidět využívaná oprávnění, je zde vidět, jakou program používá ikonu (to je ten hexa kód, který se dá dohledat v části se zdroji – Resources Table). Jsou zde vidět jednotlivé aktivity, parametry aktivit a definované intent-filtry a také využívané knihovny – prostě kompletní manifest, akorát jinak formátovaný. Pro naše další potřeby potřebujeme vstupní bod do aplikace, což bude LAUNCHER a ten je nastaven pro třídu MainActivity, která je v balíčku cz.shmoula.pat, bude se nám hodit za chvíli. Za zmínku ještě stojí anotherMapProcess v poslední aktivitě – dvě mapy se v jedné aplikaci nesnesou, tudíž je nutné je spouštět v rámci jiného procesu, tento parametr tohle zajišťuje (taky jsem nad tím tenkrát dlouho koumal).

Android manifest:
N: android=http://schemas.android.com/apk/res/android
  E: manifest (line=2)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="0.1" (Raw: "0.1")
    A: package="cz.shmoula.pat" (Raw: "cz.shmoula.pat")
    E: uses-permission (line=5)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
    E: application (line=7)
      A: android:label(0x01010001)=@0x7f040001
      A: android:icon(0x01010002)=@0x7f020000
      E: activity (line=8)
        A: android:name(0x01010003)=".MainActivity" (Raw: ".MainActivity")
        E: intent-filter (line=9)
          E: action (line=10)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=11)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
      E: activity (line=15)
        A: android:name(0x01010003)=".LookAroundActivity" (Raw: ".LookAroundActivity")
. . .
      E: activity (line=16)
        A: android:theme(0x01010000)=@0x1030006
        A: android:name(0x01010003)=".ShowAreaMapActivity" (Raw: ".ShowAreaMapActivity")
        A: android:process(0x01010011)=":anotherMapProcess" (Raw: ":anotherMapProcess")
      E: uses-library (line=22)
        A: android:name(0x01010003)="com.google.android.maps" (Raw: "com.google.android.maps")

V této fázi tedy máme hrubý přehled o struktuře aplikace, bylo by vhodné se dostat ke třídám. Ty jsou zabalené uvnitř souboru classes.dex, který se nacházi uvnitř APKčka, takže bych prosil rozzipovat tento archiv. Tento soubor je víceméně nějakým způsobem rozsypaný Java ARchive a pro jeho sesypání do použitelného formátu slouží utilitka dex2jar, která se spouští jednoduchým způsobem:

vbalak@vbalak-desktop:~/develop/stuff/dex2jar-0.0.7.8-SNAPSHOT$ ./dex2jar.sh classes.dex
version:0.0.7.8-SNAPSHOT
3 [main] INFO pxb.android.dex2jar.v3.Main - dex2jar classes.dex -&gt; classes.dex.dex2jar.jar
Done.

Získali jsme tedy opravdový archiv .jar, který je opět možné rozzipovat a koukat na jednotlivé zkompilované třídy. Také je možné udělat víc – pomocí některého java dekompileru je možné se podívat dovnitř, já volil Java Decompiler. Pomocí něj je možné otevřít získaný .jar soubor a v levé části otevřít požadovanou třídu (výše jsme zjistili, že vstupním bodem je MainActivity) a užívat pocitu vítězství, nebo začít vykrádat se začít inspirovat cizím kódem. Toď vše, pro ilustraci ještě přikládám shot JavaDecompileru.

JavaDecompiler - screenshot

JavaDecompiler - screenshot

Jak se dostat na zabezpečené služby státní správy?

0

Pod mírně bulvárním názvem tohoto blogpostu nehledejte žádný návod na hákování webů státních institucí. Pouze jsem se snažil vytvořit klienta (v Javě) pro komunikaci s webovými službami nalézajícími se na serveru, který je zajištěn certifikátem vydaným certifikační autoritou PostSignum a aby to nebylo tak jednoduché, tak uživatel je autentikován na základě osobního certifikátu.

Každý slušný člověk začne nejprve googlením, kdy najde nejeden příklad navazování spojení pomocí https (řekněme HTTP over TLS), další googlení potom poodkryje další příklady, tentokrát jak použít klientský certifikát. Na tomto základě už je možné začít stavět, nejprve tedy metoda pro získání ssl socket factory, kterou použijeme pro kontrolu identity:

private SSLSocketFactory getFactory(File pKeyFile, String pKeyPassword) throws...{
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    InputStream keyInput = new FileInputStream(pKeyFile);

    keyStore.load(keyInput, pKeyPassword.toCharArray());
    keyInput.close();
    keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());

    return context.getSocketFactory();
}

Tato factory se předá do vytvářeného zabezpečeného spojení, kdy je tímto způsobem poskytnut údaj pro autentikaci uživatele na straně serveru. Tedy metoda vracející toto spojení:

public HttpsURLConnection getConnection() throws ...{
    URL url = new URL("https://server:844/");
    HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

    con.setSSLSocketFactory(getFactory(new File("cert_sign.p12"), "heslo"));

    return con;
}

Teď je možné se pomocí zavolání connect() připojit, ale protože nejsou v úložišti důvěryhodných certifikátů vložené certifikátu serveru, snaha o připojení skončí s výjimkou:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Nejprve jsem se snažil mezi důvěryhodné certifikáty importovat certifikát kořenové autority PostSignum pomocí nástroje keytool

sudo keytool -import -alias gwc.cpost.cz -keystore /etc/java-6-sun/security/cacerts -file root.pem

což však nevedlo nikam, potřeboval jsem konkrétní certifikáty konkrétního stroje a s tím mi pomohla utilitka InstallCert (zdrojový kód, takže je to důvěryhodné ;-)). Vytvořený soubor jssecacerts je vlastně původní cacerts s přidanými certifikáty požadovaného serveru, takže je možné jej nakopírovat na odpovídající místo, pomocí keytool -list se podívat, jestli je vše v pořádku a opět vyzkoušet spojení.

Spojuje se, ale to je tak celé, takže se teď zabývám následujícími odkazy:

Nový blog

0

Respektive staronový – už dlouho mi stávající blog běžící na Drupalu pil krev – spam, „rychlost“, mobilní přístup… takže když jsem stál před rozhodnutím, jestli přejít na novější verzi drupalu, nebo přejít na WordPress, bylo jasno. Váhání napomohla ještě aplikačka pro Android.

Portace se neobešla bez potíží. Nejprve jsem zkoušel provést import pomocí skriptů v Ruby vytvořených pro songbird, ale potom, co se mi podařilo přimět hostingovou společnost, aby mi zpřístupnili databázi zvenčí, jsem zjistil, že to nebude tak jednoduché. Skripty jsem z velké části přepsal, ale stejně pořád nefungovaly, tak jsem se na to vyprdl a udělal to „po svém“.

Image and video hosting by TinyPic

Po chvíli googlení jsem našel rozumné howto, podle kterého jsem přesunul databáze na lokále a potom importoval na shmoula.cz. Export databáze z Drupalu má 12Megabajtů a po převedení do WordPressu má databáze něco nad jeden megabajt. To je mi teda rozdíl!! Ale stejně, napsal jsem toho docela dost, měl bych někde namasírovat ego v Hulán-stylu :-D.

HOWTO Alfresco PHP Library and xUbuntu

5

I tried to install Alfresco PHP Library and install it on my kubuntu developing box and it’s really very easy to get it running! Instalation procedure for windows is described in Alfresco wiki, but there are some differencies.

To get it running it’s needed to install apache and php:

sudo apt-get install apache2 libapache2-mod-php5

Then you need to download PHP Library from Sourceforge. After that you can unpack it somewhere; good place is for example /usr/share/php/alfresco-php-library. Now it’s not bad to set virtual directory to that directory. So edit your /etc/apache2/conf.d/alias file and add something like this:

Alias /Alfresco/ /usr/share/php/alfresco-php-library/

<Directory /usr/share/php/alfresco-php-library/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>

Also including this directory automaticaly (/etc/php5/apache2/php.ini) should be cool:

include_path = „.:/usr/share/php/alfresco-php-library“

Now you need to restart apache:

/etc/init.d/apache2 restart

And edit configuration file in alfresco-php-library/Examples/config.php.

$repositoryUrl = "http://localhost:8080/alfresco/api";
$userName = "admin";
$password = "admin";

After all that you can try to open web browser and point it to http://localhost/Alfresco/Examples/SimpleBrowse/ and it should work 😎


Image and video hosting by TinyPic
Go to Top