воскресенье, 26 июля 2015 г.

Перенаправление стандартного вывода java System.out

Здесь ответы на 2 вопроса:
  1. Как перенаправить стандартный вывод System.out в данном случае в текстовое поле JTextArea?
  2. Как обеспечить правильное отображение русских букв при перенаправлении вывода?

Если мы хотим организовать перенаправление в текстовое поле, значит мы создаём оконное приложение, и значит главный класс нашей программы расширяет класс JFrame.

За перенаправление вывода информационных сообщений и ошибок отвечают два метода класса System:
System.setOut(printStream);
System.setErr(printStream);

Двум этим методам нужно указать некий экземпляр printStream, этим мы и воспользуемся и создадим свой класс CustomOutputStream, в котором переопределим пару методов:
public void write(byte[] buffer, int offset, int length) throws IOException
public void write(int b) throws IOException
чтобы сообщить java, куда нужно передавать байты.

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

import java.awt.BorderLayout;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class MainWindow extends JFrame {
private static final long serialVersionUID = -205695062747265013L;

public static void main(String[] args) {
// Создаём окно, панель в нём и текстовое поле для логирования
MainWindow mainWindow = new MainWindow();
JTextArea logTextArea = new JTextArea();
JPanel jContentPane = new JPanel();
jContentPane.setLayout(new BorderLayout());
mainWindow.setContentPane(jContentPane);
jContentPane.add(logTextArea, BorderLayout.CENTER);
mainWindow.setSize(800, 550);
mainWindow.setVisible(true);

// Перенаправляем стандартный вывод в текстовое поле
PrintStream printStream = new PrintStream(new CustomOutputStream(
logTextArea));
System.setOut(printStream);
System.setErr(printStream);

System.out.println("Hi from JTextAea!");
}
}

/**
 * This class extends from OutputStream to redirect output to a JTextArrea
 *
 */
class CustomOutputStream extends OutputStream {
private JTextArea textArea;

public CustomOutputStream(JTextArea textArea) {
this.textArea = textArea;
}

// @Override
// как не надо делать
// public void write(int b) throws IOException {
// // redirects data to the text area
// textArea.append(String.valueOf((char) b));
// // scrolls the text area to the end of data
// textArea.setCaretPosition(textArea.getDocument().getLength());
// }

// правильная русская кодировка
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
final String text = new String(buffer, offset, length);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
textArea.append (text);
}
});
}

@Override
public void write(int b) throws IOException {
write(new byte[] { (byte) b }, 0, 1);
}
}

четверг, 14 февраля 2013 г.

Создание приложения Spring Security с нуля

Имеем 
  • Eclipse IDE for java EE developers 4.2.1
  • apache tomcat 7.0.35
Создаём в eclipse новый проект File > New > Project > Web > Dynamic Web Project.
Project name = springsecurity


Чего хотим в конце? -
  • страничку авторизации на основе jsp, чтобы в наше приложение могли зайти только правильные пользователи, и чтобы каждому зашедшему мы могли выдать его информацию и ничью больше.
  • на всех защищённых страницах должна быть ссылка логаута, после клика по которой клиентский компьютер и сервер должны забыть, кто только что был авторизован.
В этом примере не будет ни одного java-класса, здесь рассматриваются только вопросы конфигурирования приложения с помощью xml-файлов и некоторые 
 
Эта статья является упрощением англоязычной статьи, находящейся здесь.

По этой ссылке скачиваем Spring security, распаковываем архив и в папке dist видим там около 11 jar-файлов библиотек и такое же количество соответствующих им source.jar и javadoc.jar. Кроме того в этой папке должна быть пара war-архивов. Мы будем пользоваться архивом spring-security-samples-tutorial-3.1.2.RELEASE.war (ваша версия может быть другой).

Вообще-то это вполне законченное web-приложение. Этот архив можно положить в папку webapps своего томката, запустить его и посмотреть на результат в браузере по адресу:
http://localhost:8080/spring-security-samples-tutorial-3.1.2.RELEASE

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

Поэтому разархивируем вышеуказанный war-архив и из папки WEB-INF/lib забираем все jar-ы в аналогичную папку своего проекта:
  • aopalliance-1.0.jar
  • jcl-over-slf4j-1.6.1.jar
  • jstl-1.2.jar
  • logback-classic-0.9.29.jar
  • logback-core-0.9.29.jar
  • slf4j-api-1.6.1.jar
  • spring-aop-3.0.7.RELEASE.jar
  • spring-asm-3.0.7.RELEASE.jar
  • spring-beans-3.0.7.RELEASE.jar
  • spring-context-3.0.7.RELEASE.jar
  • spring-context-support-3.0.7.RELEASE.jar
  • spring-core-3.0.7.RELEASE.jar
  • spring-expression-3.0.7.RELEASE.jar
  • spring-security-config-3.1.2.RELEASE.jar
  • spring-security-core-3.1.2.RELEASE.jar
  • spring-security-crypto-3.1.2.RELEASE.jar
  • spring-security-taglibs-3.1.2.RELEASE.jar
  • spring-security-web-3.1.2.RELEASE.jar
  • spring-web-3.0.7.RELEASE.jar
  • spring-webmvc-3.0.7.RELEASE.jar

В папке WEB-INF создаём файл web.xml:

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

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <display-name>Spring Security Tutorial Application</display-name>

    <!--
      - Location of the XML file that defines the root application context
      - Applied by ContextLoaderListener.
      -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext-security.xml
        </param-value>
    </context-param>

    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>tutorial.root</param-value>
    </context-param>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--
      - Loads the root application context of this web app at startup.
    -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--
      - Publishes events for session creation and destruction through the application
      - context. Optional unless concurrent session control is being used.
      -->
    <listener>
      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

  <!--
    - Provides core MVC application controller. Требует наличия файла bank-servlet.xml.
    -->
    <servlet>
        <servlet-name>bank</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>bank</servlet-name>
        <url-pattern>*.html</url-pattern>
     </servlet-mapping>

     <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

Поскольку мы в web.xml объявили сервлет bank, мы обязаны создать в той же папке /war/WEB-INF конфигурационный файл для него bank-servlet.xml (имя этого конфигурационного файла формируется путём дописывания к имени сервлета строки "-servlet.xml"):

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

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>


В папку src кладём xml-файл:
  • applicationContext-security.xml

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

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

<!-- тег, ответственный за очень подробное логирование при использовании spring security -->
    <debug />

    <global-method-security pre-post-annotations="enabled" />

    <http pattern="/static/**" security="none"/>
    <http pattern="/loggedout.jsp" security="none"/>

    <http use-expressions="true">
        <intercept-url pattern="/secure/extreme/**" access="hasRole('supervisor')"/>
        <intercept-url pattern="/secure/**" access="isAuthenticated()" />
        <!--
             Allow all other requests. In a real application you should
             adopt a whitelisting approach where access is not allowed by default
          -->
        <intercept-url pattern="/**" access="permitAll" />
        <form-login />
        <logout logout-success-url="/loggedout.jsp" delete-cookies="JSESSIONID"/>
        <remember-me />
<!--
    Uncomment to enable X509 client authentication support
        <x509 />
-->
        <!-- Uncomment to limit the number of sessions a user can have -->
        <!-- 
        <session-management invalid-session-url="/timeout.jsp">
            <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
        </session-management>
 -->
    </http>

    <!--
    Usernames/Passwords are
        rod/koala
        dianne/emu
        scott/wombat
        peter/opal
    -->
    <beans:bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>

    <authentication-manager>
        <authentication-provider>
            <password-encoder ref="encoder"/>
            <user-service>
                <user name="rod" password="4efe081594ce25ee4efd9f7067f7f678a347bccf2de201f3adf2a3eb544850b465b4e51cdc3fcdde" authorities="supervisor, user, teller" />
                <user name="dianne" password="957ea522524a41cbfb649a3e293d56268f840fd5b661b499b07858bc020d6d223f912e3ab303b00f" authorities="user,teller" />
                <user name="scott" password="fb1f9e48058d30dc21c35ab4cf895e2a80f2f03fac549b51be637196dfb6b2b7276a89c65e38b7a1" authorities="user" />
                <user name="peter" password="e175750688deee19d7179d444bfaf92129f4eea8b4503d83eb8f92a7dd9cda5fbae73638c913e420" authorities="user" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>



Так же из этого проекта из папки \war возьмём  jsp-файлы и css, считая, что их кастомизация - это отдельная деятельность, которая мало связана с настройкой безопасности. Копируем файлы:
  • index.jsp - стартовая страница приложения
  • loggedout.jsp - страница, куда будет перенаправлен пользователь после логаута
  • timeout.jsp - страница, куда будет перенаправлен пользователь после истечения времени жизни сессии (и какой-то другой недействительности сессии)
  • secure/index.jsp - безопасная страница, доступ к ней предоставляется только когда метод isAuthenticated() возвращает true
  • secure/extreme/index.jsp - особо безопасная страница для пользователей с заданной ролью supervisor
  • static/css/tutorial.css - файл стилей приложения

index.jsp

<%@ page session="false" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <link rel="stylesheet" href="<c:url value='/static/css/tutorial.css'/>" type="text/css" />
      <title>Home Page</title>
  </head>
<body>
<div id="content">
<h1>Home Page</h1>
<p>
Anyone can view this page.
</p>
<p>
While anyone can also view the <a href="listAccounts.html">list accounts</a> page, you must be authorized to post to an Account from the list accounts page.
</p>
<p>
Your principal object is....: <%= request.getUserPrincipal() %>
</p>
<sec:authorize url='/secure/index.jsp'>
<p>
You can currently access "/secure" URLs.
</p>
</sec:authorize>
<sec:authorize url='/secure/extreme/index.jsp'>
<p>
You can currently access "/secure/extreme" URLs.
</p>
</sec:authorize>

<p>
<a href="secure/index.jsp">Secure page</a></p>
<p><a href="secure/extreme/index.jsp">Extremely secure page</a></p>
</div>
</body>
</html>

loggedout.jsp
<%@page session="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <link rel="stylesheet" href="<c:url value='/static/css/tutorial.css'/>" type="text/css" />
      <title>Logged Out</title>
  </head>
<body>
<div id="content">
<h2>Logged Out</h2>
<p>
You have been logged out. <a href="<c:url value='/'/>">Start again</a>.
</p>
</div>
</body>
</html>

timeout.jsp
<%@page session="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <link rel="stylesheet" href="<c:url value='/static/css/tutorial.css'/>" type="text/css" />
      <title>Session Timeout</title>
  </head>
<body>
<div id="content">
<h2>Invalid Session</h2>

<p>
Your session appears to have timed out. Please <a href="<c:url value='/'/>">start again</a>.
</p>
</div>
</body>
</html>

secure/index.jsp
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <link rel="stylesheet" href="<c:url value='/static/css/tutorial.css'/>" type="text/css" />
      <title>Secure Page</title>
  </head>
<body>
<div id="content">

<h1>Secure Page</h1>
<p>
This is a protected page. You can get to me if you've been remembered,
or if you've authenticated this session.
</p>
<p>
<sec:authorize access="hasRole('supervisor')">
    You are a supervisor! You can therefore see the <a href="extreme/index.jsp">extremely secure page</a>.<br/><br/>
</sec:authorize>
</p>
<h3>Properties obtained using &lt;sec:authentication /&gt; tag</h3>
<table border="1">
<tr><th>Tag</th><th>Value</th></tr>
<tr>
<td>&lt;sec:authentication property='name' /&gt;</td><td><sec:authentication property="name"/></td>
</tr>
<sec:authorize access="isAuthenticated()">
<tr>
<td>&lt;sec:authentication property='principal.username' /&gt;</td><td><sec:authentication property="principal.username"/></td>
</tr>
<tr>
<td>&lt;sec:authentication property='principal.enabled' /&gt;</td><td><sec:authentication property="principal.enabled"/></td>
</tr>
<tr>
<td>&lt;sec:authentication property='principal.accountNonLocked' /&gt;</td><td><sec:authentication property="principal.accountNonLocked"/></td>
</tr>
</sec:authorize>
</table>

<p><a href="../">Home</a></p>
<p><a href="../j_spring_security_logout">Logout</a></p>
</div>
</body>
</html>

secure/extreme/index.jsp
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <link rel="stylesheet" href="<c:url value='/static/css/tutorial.css'/>" type="text/css" />
      <title>Secure Page</title>
  </head>
<body>
<div id="content">
<h1>VERY Secure Page</h1>
This is a protected page. You can only see me if you are a supervisor.

<authz:authorize access="hasRole('supervisor')">
   You have authority "supervisor" (this text is surrounded by &lt;authz:authorize&gt; tags).
</authz:authorize>

<p><a href="../../">Home</a></p>
<p><a href="../../j_spring_security_logout">Logout</a></p>
</div>
</body>
</html>

static/css/tutorial.css

body {
    font-family:"Palatino Linotype","Book Antiqua",Palatino,serif;
}

#content {
    margin: 5em auto;
    width: 40em;
}

.securityHiddenUI, .securityHiddenUI * {
    background-color: #ff4500;
}


Итоговая структура файлов и папок должна выглядеть так:

пятница, 18 января 2013 г.

создать образ DVD в Windows 7

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

После этого можно сделать копию DVD  с помощью этой же программы или с помощью встроенного средства Windows 7 записо образов дисков.

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

phonegap android навигация между страницами

Пример простого приложения на phonegap, в котором мы хотим реализовать программную навигацию между страницами. По хардверной кнопке андроида "Назад" будет происходить выход из приложения, так как мы будем работать внутри одной страницы index.html с созданием соотвественно одного Activity в приложении.

Создаём проект как описано здесь.

Для реализации указанной цели в index.html используются иерархически вложенные <div>. В корне тега <body> вставляется тег <div data-role="page" id="page" data-theme="a">. Он будет содержать набор всех хедерев, всех страниц и всех футеров нашего приложения.

Пример хедера:

        <div data-role="header">
            <h1>Header text</h1>
        </div>


Тег со списком всех страниц: <div class="page-content">. Внутри него перечисляются все страницы, которые будут использованы в приложении:

            <div data-role="content" id='page1'>

                <h1>Content here...</h1>

                <input type='submit' onclick='switchPage("#page2");' value='go to page 2'></input>

            </div>
            <div data-role="content" id='page2' style="display:none;">
                <h1>More content here...</h1>
                <input type='submit' onclick='switchPage("#page1");' value='go to page 1'></input>
            </div>


Пример футера:

        <div data-role="footer"  data-position="fixed" data-theme="c" vertical-align="middle" align="center">
            <h4>Footer text</h4>
        </div>

В скрипте в начале тега <body> нужно определить метод переключения страниц:
<script>
function switchPage(name){
$('#page1').hide();
$('#page2').hide();
$(name).show();
}
</script>



Итоговый файл index.html выглядит так:
<!DOCTYPE HTML>
<html>
<head>
<title>PhoneGap</title>
<meta charset="utf-8">
<link rel="stylesheet" href="jquery.mobile-1.1.0.min.css" />
<script type="text/javascript" charset="utf-8" src="cordova-2.0.0.js"></script>
<script type="text/javascript" src="jquery-1.6.4.min.js"></script>
<script type="text/javascript" src="jquery.mobile-1.1.0.min.js"></script>
</head>

<body onload="init()">
<script>
function switchPage(name){
$('#page1').hide();
$('#page2').hide();
$(name).show();
}
</script>
    <div data-role="page" id="page" data-theme="a">
        <div data-role="header">
            <h1>Header text</h1>
        </div>
        <div class="page-content">
            <div data-role="content" id='page1'>
                <h1>Content here...</h1>
                <input type='submit' onclick='switchPage("#page2");' value='go to page 2'></input>
            </div>
            <div data-role="content" id='page2' style="display:none;">
                <h1>More content here...</h1>
                <input type='submit' onclick='switchPage("#page1");' value='go to page 1'></input>
            </div>
        </div>
        
        <div data-role="footer"  data-position="fixed" data-theme="c" vertical-align="middle" align="center">
            <h4>Footer text</h4>
        </div>
    </div>
</body>
</html>




вторник, 4 декабря 2012 г.

cordova android PUSH plugin

Создание плагинов cordova (phonegap) для android на примере PUSH сервиса.


  1. Перед реализацией плагина нужно убедиться, что библиотеки cordova-2.0.0.jar, gcm.jar, json-simple-1.1.1.jar лежат в папке libs, и с этими файлами проделана процедура Add to Build Path (они должны быть в списке Referenced Libraries, иначе надо кликнуть по библиотеке правой кнопкой и выбрать Build Path / Add to Build Path).
  2. В файле res/xml/config.xml в тэге <plugins> декларируем создаваемый на java плагин:
    <plugin name="PushPlugin" value="ru.andrew.plugin.PushPlugin"/>
     Что означает, что java-класс будет называться 
    PushPlugin, и находиться он будет  в пакете ru.andrew.plugin.

  3. Прописываем необходимые разрешения в конфигурационном файле приложения AndroidManifest.xml;

     В корневом тэге <manifest> прописываем разрешения:
        <uses-permission android:name="ru.andrew.androidnativepush.permission.C2D_MESSAGE" />
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    В тэге <application> прописываем receiver и service:
            <receiver
                android:name="com.google.android.gcm.GCMBroadcastReceiver"
                android:permission="com.google.android.c2dm.permission.SEND" >
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                    <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

                    <category android:name="ru.andrew.androidnativepush" />
                </intent-filter>
            </receiver>
            
            <service android:name=".GCMIntentService" />

    в этих кусках кода нужно всюду имя пакета ru.andrew.androidnativepush заменить на своё, которое мы указываем в самом верху AndroidMainfest.xml в атрибуте package.
  4. Создаём java-класс плагина:
    package ru.andrew.plugin;

    import java.io.IOException;

    import org.apache.cordova.api.Plugin;
    import org.apache.cordova.api.PluginResult;
    import org.json.JSONArray;
    import org.json.JSONException;

    import android.content.Context;
    import android.net.ConnectivityManager;
    import android.net.NetworkInfo;
    import android.util.Log;

    import com.google.android.gcm.GCMRegistrar;

    public class PushPlugin extends Plugin {
    private static final String TAG = "PushPlugin";

    public PluginResult execute(String action, JSONArray args, String callbackId) {
    try {
    if ("echo".equals(action)) {
    return echo(args);
    } else if ("registerPush".equals(action)) {
    return registerPush(args);
    } else if ("unregisterPush".equals(action)) {
    return unregisterPush(args);
    } else {
    return new PluginResult(PluginResult.Status.INVALID_ACTION);
    }
    } catch (JSONException ex) {
    ex.printStackTrace();
    return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
    }
    }

    private PluginResult unregisterPush(JSONArray args) {
    /*
    Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
    unregIntent.putExtra("app", PendingIntent.getBroadcast(cordova.getActivity()
    .getApplicationContext(), 0, new Intent(), 0));
    cordova.getActivity()
    .getApplicationContext().startService(unregIntent);
    */
    try {
    GCMRegistrar.unregister(cordova.getActivity()
    .getApplicationContext());
    Log.d(TAG, "PUSH уведомления дерегистрированы.");
    return new PluginResult(PluginResult.Status.OK);
    } catch (Exception ex) {
    ex.printStackTrace();
    return new PluginResult(PluginResult.Status.ERROR, ex.getMessage());
    }
    }

    private PluginResult echo(JSONArray args) throws JSONException {
    String echo = args.getString(0);
    if (echo != null && echo.length() > 0) {
    return new PluginResult(PluginResult.Status.OK, echo);
    } else {
    return new PluginResult(PluginResult.Status.ERROR);
    }
    }

    private PluginResult registerPush(JSONArray args) throws JSONException {
    String projectIdStr = args.getString(0);
    if (isBlank(projectIdStr)) {
    return new PluginResult(PluginResult.Status.ERROR,
    "project id must be set in parameters");
    } else {
    try {
    long projectId = Long.parseLong(projectIdStr);
    String deviceId = registerPush(projectId);
    return new PluginResult(PluginResult.Status.OK, deviceId);
    } catch (NumberFormatException ex) {
    ex.printStackTrace();
    return new PluginResult(PluginResult.Status.ERROR,
    "project id must be wellformed number");
    } catch (IOException ex) {
    ex.printStackTrace();
    return new PluginResult(PluginResult.Status.ERROR,
    ex.getMessage());
    }
    }
    }

    private String registerPush(long projectId) throws IOException {
    inetIsOk();
    GCMRegistrar.checkDevice(cordova.getActivity().getApplicationContext());
    GCMRegistrar.checkManifest(cordova.getActivity()
    .getApplicationContext());
    String regId = GCMRegistrar.getRegistrationId(cordova.getActivity()
    .getApplicationContext());
    if (regId.equals("")) {
    GCMRegistrar.register(
    cordova.getActivity().getApplicationContext(), projectId
    + "");
    Log.i(TAG,
    "just now registered: "
    + GCMRegistrar.getRegistrationId(cordova
    .getActivity().getApplicationContext()));
    regId = GCMRegistrar.getRegistrationId(cordova.getActivity()
    .getApplicationContext());
    return regId;
    } else {
    Log.e(TAG, "Already registered: " + regId);
    return regId;
    }
    }

    public void inetIsOk() throws IOException {
    ConnectivityManager connMgr = (ConnectivityManager) cordova
    .getActivity().getApplicationContext()
    .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
    return;
    //
    } else {
    throw new IOException("Нет интернета");
    }
    }

    public static boolean isBlank(String s) {
    if (s == null || "".equals(s.trim()))
    return true;
    else
    return false;
    }
    }

  5. Создаём java-класс GCM-сервиса, реализующие callback-функции регистрации / дерегистрации в сервисе, получения сообщения и получения ошибки.
    Этот класс в соответствии с 5 пунктом 2 шага спецификации google должен расширять класс GCMBaseIntentService.
    Неприятным ограничением в реализуемых функциях является невозможность использования Toast-уведомлений.

    package ru.andrew.androidnativepush;

    import java.util.Random;

    import android.app.Notification;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.util.Log;

    import com.google.android.gcm.GCMBaseIntentService;

    public class GCMIntentService extends GCMBaseIntentService {

    @Override
    protected void onError(Context arg0, String arg1) {
    Log.d("GCM onError", arg1);
    }

    @Override
    protected boolean onRecoverableError(Context context, String errorId) {
    Log.d("GCM onRecoverableError", errorId);
    return false;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onMessage(Context ctx, Intent intt) {
    Log.d("onMessage", String.valueOf(intt));
    for (String s : intt.getExtras().keySet()) {
    Log.d(s, intt.getExtras().getString(s));
    }

    String ns = Context.NOTIFICATION_SERVICE;
    NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
    int icon = R.drawable.ic_launcher;// notification_icon;
    CharSequence tickerText = intt.getStringExtra("message");
    long when = System.currentTimeMillis();

    Notification notification = new Notification(icon, tickerText, when);
    notification.flags = Notification.DEFAULT_LIGHTS
    | Notification.FLAG_AUTO_CANCEL;

    CharSequence contentTitle = "myApp";
    Random r = new Random();
    int notificationId = r.nextInt();
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this,
    notificationId, notificationIntent,
    PendingIntent.FLAG_UPDATE_CURRENT);
    notification.setLatestEventInfo(ctx, contentTitle, tickerText,
    contentIntent);

    mNotificationManager.notify(notificationId, notification);
    }

    @Override
    protected void onRegistered(Context context, String arg1) {
    Log.d("onRegistered", arg1);
    Log.d("onRegistered", "Зарегистрирован в сервисе получения PUSH-уведомлений");
    // Toast.makeText(context, "Зарегистрирован в сервисе получения PUSH-уведомлений", Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onUnregistered(Context context, String arg1) {
    Log.d("onUnregistered", arg1);
    Log.d("onUnregistered", "Отключен от сервиса получения PUSH-уведомлений");
    // Toast.makeText(context, "Отключен от сервиса получения PUSH-уведомлений", Toast.LENGTH_LONG).show();
    }

    }


    Здесь функция onMessage(Context ctx, Intent intt) ответственна за поведение программы при получении нового уведомления. Переменная intt содержит данные, передаваемые в сообщении. Они могут быть получены так:
    intt.getStringExtra("message")

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

    Первый эффект реализуется кодом:
    NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    int icon = R.drawable.ic_launcher;// иконка нашего приложения
    CharSequence tickerText = intt.getStringExtra("message");
    long when = System.currentTimeMillis();
    Notification notification = new Notification(icon, tickerText, when);
    notification.flags = Notification.DEFAULT_LIGHTS
    | Notification.FLAG_AUTO_CANCEL;
    к уведомлению будет прикреплена иконка нашего приложеиня icon (чтобы пользователь знал, куда его хотят отправить :) ), и будет прокручен текст tickerText, содержащийся в поле message сообщения. Параметр when говорит, когда нужно показать уведомление, поскольку мы хотим здесь и сейчас, мы ставим текущее время (при обработке сформированного уведомления системой время уже будет немного больше, что наверное означает, что показываются все уведомления с парметром времени меньшим чем текущее. Но это домыслы автора, не подкреплённые авторитетными ссылками или опытом).
    Флаг Notification.FLAG_AUTO_CANCE означает, что иконка приложения будет исчезать из строки статуса при прочтении сообщения. Как оказывается, это не дефолтное поведение уведомления, что меня несколько удивило.

    Второй эффект реализуется кодом:
    CharSequence contentTitle = "myApp";
    Random r = new Random();
    int notificationId = r.nextInt();
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this,
    notificationId, notificationIntent,
    PendingIntent.FLAG_UPDATE_CURRENT);
    notification.setLatestEventInfo(ctx, contentTitle, tickerText,
    contentIntent);
    mNotificationManager.notify(notificationId, notification);
    при создании notificationIntent мы должны указать класс активити MainActivity.class, на которую должен будет перейти пользователь при клике на уведомление в списке уведомлений.
    notificationId - это уникальный номер сообщения в нашей системе, Поскольку обычно у нас есть сервер, хранящий все сообщения всем пользователям, этот параметр будет равен id этого сообщения в базе. Он должен лежать в одном из полей сообщения и браться оттуда, Здесь он берётся как случайное число, потому что если задать его константой, а потом несколько раз послать сообщения, система будет думать, что ей несколько раз приходит одно и то же сообщение, и не будет показывать их как новые сообщения.

    Конструктор new Notification(icon, tickerText, when) и метод notification.setLatestEventInfo(ctx, contentTitle, tickerText, contentIntent) устарели начиная с 17 версии SDK. Но на момент написания этой статьи этой версии нет и месяца, что означает, что у многих устройств сейчас замена им не будет поддерживаться и в соотвествующих местах кода приложение будет выбрасывать исключение вместо того, чтобы скромно проиллюстрировать работу такой желанной функциональности. Поэтому здесь я выбрал воспользоваться объявленными устаревшими методами. Можно было бы программно запросить у системы номер SDK и в зависимости от того, переваливает ли она вышеуказанный порог или нет выполнять соответственно новый или старый код, но для целей данного поста мне кажется это неким перебором.



    Редактируем index.html
  1. Для использования русских букв в интерфейсе в тэге <head> нужно добавить строку:
    <meta charset="utf-8">

  2. Для вызова метода плагина необходимо дождаться, чтобы cordova была полностью загружена. Для этого, например, для целей тестирования в файле index.html добавим событие окончательной готовности девайса:
    document.addEventListener("deviceready", myCallbackFunction, false);
    Функция myCallbackFunction будет вызвана когда девайс будет готов.

  3. Реализуем метод myCallbackFunction:
    function myCallbackFunction(){


    cordova.exec(success, fail, "PushPlugin", "registerPush", ['999999999999'])
    }
    function success(data){
    alert('success: ' + data);
    }


    function fail(data){
    alert('failed: ' + data);
    }

    здесь:
    success(data) - метод, который будет вызван при успешном окончании метода плагина;
    fail(data) - метод, который будет вызван при неудачном окончании метода планига;
    "PushPlugin" - имя плагина, задекларированного в config.xml;
    "echo" - действие (селектор), на основании которого будет вызван тот или иной метод;
    ['999999999999'] - массив данных в формате JSON, который передаётся методу плагина. В данной плагине это номер проекта, зарегистрированного в GCM сервисе google.

  4. Запускаемся, получаем множество ошибок, мужественно их преодолеваем и идём пить кофе.