Android: Cleartext HTTP traffic to downloads.bbc.co.uk not permitted

Od Android 8 (Oreo) je defaultně zakázáno komunikovat po http – je nutné komunikovat po https. Pokud stahujete data ze zdroje, kde nemůžete ovlivnit nasazení https, lze toto pravidlo obejít přidáním níže uvedeného kódu do vaší aplikace.

Vytvoříme soubor: res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

Do AndroidManifest.xm přidáme řádek: http://android:networkSecurityConfig=“@xml/network_security_config“

<application
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:networkSecurityConfig="@xml/network_security_config"
...

 

Debugování Retrofit požadavků

O tom, jak zprovoznit Retrofit jsme si již psali. Dnes se podíváme na to, jak získat více informací o tom, jak Retrofit funguje a jaké posílá a přijímá požadavky.

Začneme nainstalováním závislosti: com.squareup.okhttp3:logging-interceptor

implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(logging)
    .build();

Kde setLevel určuje úroveň logování: NONE, BASIC, HEADERS, BODY

Tím máme základní logování zprovozněno:

10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: --> POST https://vencax.local/mwm/api/login
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Content-Type: application/json; charset=UTF-8
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Content-Length: 107
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Authorization: Basic xxx
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: {"actualDateTime":"2018-10-11T12:46:10.2580000+02:00","phoneId":"2c3c2a5fe5a7b529","versionNumber":"1.9.0"}
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: --> END POST (107-byte body)

Pokud chcete logovat svým vlastním způsobem, můžete přetížit konstruktor:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new 
Logger() {
@Override public void log(String message) {
    Log.d(TAG, "message: ");
    }
});

 

Jak stáhnout .apk z Google Play

Klient má Android zařízení, které jsou pouze v interní síti, bez přístupu na internet. Přál by si na tyto zařízení dostat aplikace z Google Play. Jde to?

Ano, jde 🙂 Z Google Play je možné stáhnout .apk soubor, který se nainstaluje na zařízení. Existují na to nejrůznější webové stránky – ty mi ovšem většinou nefungují. Nejlepší zkušenosti mám s doplňkem do Chrome, APK Downaloader. Stačí nainstalovat do Chrome na PC, v Chrome najít aplikaci, zkopírovat URL do pluginu a stáhnout

 

Chamsys – ArtNet

ArtNet je protokol pro přenos DMX protokolu a RDP (Remote Device Management) po UDP (User Datagram Protocol) v IP (Internet Protocol). Zjednodušeně: vaše DMX nemusíte na stage táhnout několika linkami (universama) ale všechny najednou po „počítačovém síťovém“ kabelu. Na stage máte krabičku, ko které je tonto „počítačový síťový kabel“ zapojen. Z této krabičky už klasicky rozvedete jednotlivé DMX kabely do požadovaných světel.

Začínáme s ArtNet a ChamSys

Jak tedy začít? Já začal s převodníkem ArtNet – DMX 3 universe od http://www.blackout.sk. Převodník cenově vychází na 70€ a nabízí 3 universy.

Tento převodník má defaultně nastavenou IP: 192.168.0.99 a masku 255.255.255.0  Pro přihlášení do převodníku použijte tyto defaultní přístupové údaje: user: admin heslo: 12345  Není nutné ho nastavovat, po zakoupení je již vše nastaveno. Pouze mu přenastavíme IP a masku sítě na:

  • IP: 2.139.240.99
  • Maska: 255.0.0.0

Kompletní návod k ArtNet převodníku od blackout.sk

Proč jsem vybral ChamSys MagicQ PC

Pokud začínáte, vyprdněte se na Freestyler DMX a podobné programy pro začátečníky a jděte rovnou do něčeho na úrovni. Profíci s penězma valí MA lighting. Zbytek osvětlovačů, co se nechtějí učit nic nového (podle mne 🙂 ) valí Avolites – je to rider a všichni s tím umí. Já, vyzkoušel několik programů (především zdarma) ale jakmile jsem objevil ChamSys, zamiloval jsem se. Koketoval jsem i s Avolites Titan One ale nesednul mi.

Pokud nemáte peníze, pro začátek je ChamSys super v tom, že stačí ArtNet převodník a můžete používat 1 universe zcela zdarma (na ovládání světel). Nemáte žádné omezení na funkčnost. Pak až něco vyděláte, přikoupíte PC Wing Compact, pak Extra Wing Compact a s MagicQ MQ80 už máte dospělý pult bez nutnosti PC a můžete svítit vysokou osvětlovačskou ligu 🙂

Pojďme nastavit MagicQ PC na počítači

Stáhnete na PC MagicQ PC a nainstalujte. Náš ArtNet – DMX 3 universe má IP: 2.139.240.99, našemu PC kde máme nainstalovaný MagicQ PC nastavíme IP: 2.139.240.200 a ArtNet propojíme s PC síťovým kabelem.

OK, nyní spustíme MagicQ PC. Jdeme do sekce Setup -> VIEW SETTINGS -> Network kde nastavíme tyto hodnoty:

  • IP address – stejnou jako má PC (192.168.0.2)
  • Subnet mask – defaultně 255.255.255.0
  • Gateway address – není nutná ale většinou 192.168.0.1

IP adresy máme nastavené, vyzkoušíme, že se z PC dostaneme na ArtNet převodník příkazem ping 2.139.240.99

V konzoli je vidět že IP adresa 2.139.240.99 odpovídá na ping, máme správně nastaveno.

Nyní nastavíme MagicQ aby universe 1 odesílal na ArtNet 0. Jdeme do sekce Setup -> VIEW DMX I/O

Nastavili jsme pro universe 1 status na enabled a pojmenovali jsme si ho na universe1.

Adresy Artnet převodníků můžeme vidět i na obrazovce: Settings -> View status -> Art-Net Nodes. Pomůže to, pokud máte převodník a neznáte jeho adresu.

To je vše 🙂 Nyní můžete napatchovat světla a začít používat.

Android: fragment worker

Dnes se podíváme na to, jak dělat složitější operace ve fragmentu při které se bude volat metoda z activity. Zaměříme se na to, jak správně napsat kód aby aplikace nespadla při otočení displeje.

Jednoduchá ukázka jak to nedělat:

MainActivity.java – klasická activity, která má veřejnou metodu worker, která by v praxi vykonávala nějakou operaci. Nyní pro ukázku vypíše pouze text do logu.

package cz.vencax.mobilapp.dockstatesapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void worker(String text) {
        Log.d("xxx", "text: " + text);
    }
}

LoginFragment.java – jednoduchý fragment s tlačítkem. Po tapnutí na tlačítko, se spustí AsyncTask, který volá metodu worker z MainActivity

package cz.vencax.mobilapp.dockstatesapplication;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class LoginFragment extends Fragment {

    public LoginFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        //return inflater.inflate(R.layout.fragment_login, container, false);
        View view = inflater.inflate(R.layout.fragment_login, container, false);

        Button button = (Button) view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                new LoginAndParseInspectionTask().execute();
            }
        });

        return view;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    class LoginAndParseInspectionTask extends AsyncTask<String, String, Void> {
        @Override
        protected Void doInBackground(String... strings) {
            for(int i = 0; i < 1000000000; i++) {
                ((MainActivity)getActivity()).worker("run" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

Pokud tento kód spustíte, zjistíte, že funguje. Bohužel po otočení displeje aplikace spadne….

Jak na to lépe?

Správná cesta je vytvoření static interface TaskCallbacks ve fragmentu, který implementujeme v activity. Metodu worker následně voláme přes mCallbacks.worker(„run“ + i);

MainActivity.java

package cz.vencax.mobilapp.dockstatesapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity implements LoginFragment.TaskCallbacks {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void worker(String text) {
        Log.d("xxx", "text: " + text);
    }
}

LoginFragment.java

package cz.vencax.mobilapp.dockstatesapplication;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class LoginFragment extends Fragment {

    static interface TaskCallbacks {
        void worker(String text);
    }

    private TaskCallbacks mCallbacks;

    public LoginFragment() {
        // Required empty public constructor
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (!(context instanceof TaskCallbacks)) {
            throw new IllegalStateException("Activity must implement the TaskCallbacks interface.");
        }

        // Hold a reference to the parent Activity so we can report back the task's
        // current progress and results.
        mCallbacks = (TaskCallbacks) context;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        //return inflater.inflate(R.layout.fragment_login, container, false);
        View view = inflater.inflate(R.layout.fragment_login, container, false);

        Button button = (Button) view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                new LoginAndParseInspectionTask().execute();
            }
        });

        return view;
    }


    class LoginAndParseInspectionTask extends AsyncTask<String, String, Void> {
        @Override
        protected Void doInBackground(String... strings) {
            for(int i = 0; i < 1000000000; i++) {
                //((MainActivity)getActivity()).worker("run" + i);
                mCallbacks.worker("run" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

Teď již při otáčení displeje není problém.

Retrofit: synchronní a asynchronní požadavky

Minule jsme si ukázali, jak použít Retrofit v Android studiu. Dnes se podíváme na synchronní a asynchronní požadavky.

Synchronní požadavek

Při synchronním požadavku, je na server odeslán požadavek, aplikace se „zastaví“ a čeká, dokud neobdrží odpověď od serveru.

Asynchronní požadavek

Při asynchronním požadavku se používají tzv. callbacky. Na server se odešle požadavek (stejně jako při synchronním požadavku) ale nečeká se na odpověď a kód pokračuje dál. Na odpověď ze serveru čeká callback, který provede požadovanou operaci s odpovědí od serveru. Tento callback se může zavolat kdykoliv (odpověď od serveru trvá různě dlouho).

V kódu je vidět asynchronní volání, které je shodné jako v prvním seznámení s Retrofit, přibylo synchronní volání:

package cz.vencax.retrofit;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.IOException;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button button = findViewById(R.id.buttonAsynchronous);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                getBooksFromApiAsynchronous();
            }
        });

        final Button buttonPost = findViewById(R.id.buttonSynchronous);
        buttonPost.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try {
                    getBooksFromApiSynchronous();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Asynchronous Retrofit request
     */
    private void getBooksFromApiAsynchronous() {

        Log.d("xxx", "getBooksFromApiAsynchronous");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> books = bookService.getAllBooks();
        books.enqueue(new Callback<Books>() {

            @Override
            public void onResponse(Call<Books> call, Response<Books> response) {
                Log.d("xxx", "Asynchronous onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "Asynchronous response.isSuccessful()" + response.body());
                    Books books = response.body();

                    Log.d("xxx", "Asynchronous response.isSuccessful(), books count: " + books.getBooks().size());
                    for (Book book : books.getBooks()) {
                        Log.d("xxx", "Asynchronous Book: " + book.getTitle() + ", " + book.getAuthor());
                    }
                } else {
                    Log.d("xxx", "Asynchronous response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Books> call, Throwable t) {
                Log.d("xxx", "Asynchronous onFailure: " + t);
            }
        });
    }

    /**
     * Synchronous Retrofit request
     */
    private void getBooksFromApiSynchronous() throws IOException {

        Log.d("xxx", "getBooksFromApiSynchronous");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> call = bookService.getAllBooks();
        Books books = call.execute().body();

        Log.d("xxx", "Synchronous response.isSuccessful(), books count: " + books.getBooks().size());
        for (Book book : books.getBooks()) {
            Log.d("xxx", "Synchronous Book: " + book.getTitle() + ", " + book.getAuthor());
        }
    }
}

 

 

Android se nepřipojuje k WiFi síti bez internetu

Android od verze KitKat (4.4) se automaticky nepřipojuje k WiFi sítím, u kterých nezjistí dostupnost internetu. Asi to dává smysl, proč by se pojil k síti, která nemá internet? Raději zkusí jinou síť kde internet bude. Ale jak to vyřešit pokud chceme aby s k této síti připojoval? Uděláme malý hack, kterým nasimulujeme servery, na kterých Android zjišťuje, že je na internetu 🙂 Android po připojení zkontroluje URL (je natvrdo zadrátovaná v Androidu a může se měnit v závislosti na verzi Androidu). Pokud dostane zpět požadovanou odpověď, považuje WiFi síť za připojenou k internetu (i když internet nemá) 🙂

Požadavky na danou adresu jsou klasickým GETem a zařízení požaduje odpověď 204 (No content) to je celé. Jenže jak si tyto servery nasimulovat?

Nejjednodušší je v DNS překládat URL (clients3.google.com a connectivitycheck.gstatic.com) na náš server, kde spustíme nginx s touto konfigurací:

vim /etc/nginx/sites-enabled/default
# ====================================
# = android internet hack
# ====================================

server {
    server_name
        clients3.google.com
        connectivitycheck.gstatic.com;
    listen 80;

    location /generate_204 {
        return 204;
    }
}

 

Základní php funkce pro vývojáře WordPressu

Když není do čeho píchnout, dělám malé weby mám chvilku, dělám webíky pro známé. Dělám svoje vlastní šablony a během tvorby jsem se setkal s těmito specialitami:

Jak přidat do menu (main-menu) class nav-link?

//add class="nav-link" to href in li in menu
add_filter( 'nav_menu_link_attributes', 'add_specific_menu_location_atts', 10, 3 );
function add_specific_menu_location_atts( $atts, $item, $args ) {
	// check if the item is in the primary menu
	if( $args->theme_location == 'main-menu' ) {
		// add the desired attributes:
		$atts['class'] = 'nav-link';
	}
	return $atts;
}

V administraci pro pro uživatele id 2 skryj položky z menu:

function remove_menus(){
	if(get_current_user_id() == 2) {
		//hide for user id 2
		remove_menu_page('index.php');                  //Dashboard
		remove_menu_page('jetpack');                    //Jetpack*
		remove_menu_page('edit.php');                   //Posts
		//remove_menu_page('upload.php');                 //Media
		//remove_menu_page('edit.php?post_type=page');    //Pages
		remove_menu_page('edit-comments.php');          //Comments
		remove_menu_page('themes.php');                 //Appearance
		remove_menu_page('plugins.php');                //Plugins
		remove_menu_page('users.php');                  //Users
		remove_menu_page('tools.php');                  //Tools
		remove_menu_page('options-general.php');        //Settings
		remove_menu_page('WP-Lightbox-2');        		//WP Lightbox 2
	}
}
add_action( 'admin_menu', 'remove_menus' );

Jak v administraci použít vlastní css styl pro editor?

add_action( 'init', 'load_custom_wp_admin_editor_styles' );
function load_custom_wp_admin_editor_styles() {
	add_editor_style( get_template_directory_uri() . '/css/admin-style.css' );
}

Vytvářená galerie bude mít defaultně předvolené 4 sloupce fotek:

/**
 * Default 4 colums in gallery
 * @param $settings
 * @return mixed
 */
function theme_gallery_defaults( $settings ) {
	$settings['galleryDefaults']['columns'] = 4;
	return $settings;
}
add_filter( 'media_view_settings', 'theme_gallery_defaults' );

Vytvářená galerie bude mít automaticky předvolen URL odkazu na mediální soubor:

/**
 * Gallery default URL odkazu: medialni soubor
 */
add_filter( 'shortcode_atts_gallery',
	function( $out ){
		$out['link'] = 'file';
		return $out;
	}
);

U nahrávání fotek do mediálních souborů odeber titulek fotky a popis (některé foťáky tam ukládají nesmysly):

/**
 * Automatically set the title as caption to empty
 *
 * @see https://wordpress.stackexchange.com/a/188708/26350
 */
add_filter( 'wp_insert_attachment_data', function( $data, $postarr )
{
	// Let's target only the uploading process and not the updating of attachments:
	if( isset( $postarr['ID'] ) && 0 == $postarr['ID'] ) {
		$data['post_excerpt'] = "";
		$data['post_title'] = "";
	}

	return $data;
}, 10, 2 );

Automatická aktualizace:

//auto actualise
//plusing
add_filter( 'auto_update_plugin', '__return_true' );
//themes
add_filter( 'auto_update_theme', '__return_true' );
//wp
define( 'WP_AUTO_UPDATE_CORE', true );