четверг, 10 января 2013 г.

Мой погодный виджет

 Не так давно пришла зима и стало актуально заглядывать на сайты с погодой. И тут пришла идея моему мужу - написать мне виджет, который отображает текущую погоду нашего города с одного сайта. Почитала несколько статей и вот что вышло.
 Я предполагаю, что вы уже знаете как создавать проекты и поэтому переходим к созданию виджета.
AndroidManifest.xml. Просто заменяем все его содержимое на вот этот код:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 
      package="com.example.simpleactivity"
 
      android:versionCode="1"
      android:versionName="1.0">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
    <application android:icon="@drawable/fly" android:label="@string/app_name">
 
           <receiver android:name="SimpleActivity" >
                    <intent-filter>
                        <action android:name="ru.example.android.widget.ACTION_WIDGET_RECEIVER" />
                        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                    </intent-filter>
                    <meta-data android:name="android.appwidget.provider" 
                     android:resource="@xml/manifest" />
        </receiver> 
    </application>
    <uses-sdk android:minSdkVersion="8"/>
</manifest>

SimpleActivity.java. Тут мы будем описывать реакции нашего виджета.
 
package com.example.simpleactivity;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
 
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
 
public class SimpleActivity extends AppWidgetProvider {
 
 public static String ACTION_WIDGET_RECEIVER = "ActionReceiverWidget";
 // Метод onUpdate вызывается при обновлении виджета. 
 @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
   //Создаем новый RemoteViews
         RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
 
         //Подготавливаем Intent для Broadcast
         Intent active = new Intent(context, SimpleActivity.class);
         active.setAction(ACTION_WIDGET_RECEIVER);
         //connect
         String msg = "";
         try {
   msg = GetDegree();
   } catch (Throwable e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   msg = "Error";
   }
         active.putExtra("msg", msg);
 
         //создаем наше событие
         PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
 
         //регистрируем наше событие
         remoteViews.setOnClickPendingIntent(R.id.button1, actionPendingIntent);
         remoteViews.setTextViewText(R.id.button1,msg+" C");
 
         //обновляем виджет
         appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    } 
 
 //А в методе onReceive ловим наше «событие» и обрабатываем
 @Override
    public void onReceive(Context context, Intent intent) {
         //Ловим наш Broadcast, проверяем и выводим сообщение
         final String action = intent.getAction();
         if (ACTION_WIDGET_RECEIVER.equals(action)) {
              String msg = "null";
              try {
               msg = GetDegree();
              } catch(Throwable e) {
                    Log.e("Error", "msg = null");
                    msg = "empty";               
              }
              RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.main);
              updateViews.setTextViewText(R.id.button1,msg+" C");
//              
              ComponentName myComponentName = new ComponentName(context, SimpleActivity.class);
              AppWidgetManager manager = AppWidgetManager.getInstance(context);
              manager.updateAppWidget(myComponentName, updateViews);
         } 
 
         super.onReceive(context, intent);
    }
 
 //получить погоду
 protected String GetDegree() throws Throwable {
  String str_degree = "";
  String URL = "http://dove.opsb.ru/files/weather.js";
 
 //объект моего класса, который осуществляет интернет-запросы
  String [] res = new ConnectHttpGet().executeHttpGet(URL);
  if (res[1] == "200") {
   //pars
   int pos = res[0].indexOf(""");
   str_degree = res[0].substring(pos+1, res[0].length()-2);
  }else{
   str_degree = res[1];
  }
  return str_degree;
 }
}

Manifest.xml.  Теперь опишем наш виджет. Cоздаем папку "xml" в папке "res". В папке "xml" создаем файл manifest.xml, открываем его в редакторе и пишем следующий код:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="1800000"
    android:initialLayout="@layout/main">
</appwidget-provider>
 
updatePeriodMillis - период обновления виджета в миллисекундах. 
Каждый раз по истечении этого промежутка времени срабатывает onUpdate метод.
 
Main.xml. Создаем шаблон виджета в папке "res/layout":
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/Widget" 
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:orientation="horizontal"
     android:gravity="center_vertical"
    >
 
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Погода" />
 
</RelativeLayout>
Вот и все. Полезные статьи:
http://habrahabr.ru/post/114515/
http://ondroid.info/delaem-vidzhet-dlya-rabochego-stola/
http://developer.android.com/guide/practices/ui_guidelines/widget_design.html http://developer.android.com/guide/topics/appwidgets/index.html

суббота, 5 января 2013 г.

четверг, 4 октября 2012 г.

Android. HTTPS-соединение с клиентским сертификатом

 Поступила мне задачка: Написать приложение под Андройд, которое ходит с клиентским сертификатом через https соединение. Вроде, что может быть проще! Снабжать свои приложения возможностью выхода в сеть я научилась еще давно. И думала, что легко будет сделать тоже самое, только еще и с сертификатом. Но не тут-то было. Долго искала примеры, читала различные доки. Простое https-соединение у меня получалось. Как и думала - нет ничего проще. Но заставить приложение использовать клиентский сертификат....оказалось сложнее. Это сейчас я знаю, что все на самом деле легко. Надо googl'у  задавать правильные вопросы и в различных примерах убирать все ненужные " красявости". В самом начале моего поиска сбила с толку меня статья где-то на каком-то зарубежном сайте. Там говорилось, что необходимо взять сертификат формата *.pem, импортировать его в файл-хранилище, например типа bks,закинуть на Android-устройство и им пользоваться. И вот тут-то я свернула с верного пути. И потратила много времени на поиск и на различные тестовые реализации. Но как говорится - "Если долго мучиться что-нибудь получится" и мой кропотливый труд наконец-то был вознагражден! Спасибо, конечно, некоторым прекрасным лицам на форуме и на работе, которые помогали и подсказывали мне. И конечно спасибо удаче, которая посетила меня вчера. В итоге весь мой полуторонедельный труд уложился вот в этот кусочек кода:
private void onClickConnect(String path,String password,String https_url) {  
     //Создание файл хранилища для сертификата формата p12 
     //https_url - то, куда будем ходить 
     //path - путь к файлу на андройд-устройстве
     //password - пароль от сертификата. На многих сайтах пишут, 
     //чтобы пароль от файл-хранилища был такой же как и у сертификата.
            //Иначе будет ошибка.
     InputStream in = null;
     try {
  in = new FileInputStream(path);
     } catch (FileNotFoundException e1) {
  e1.printStackTrace();
     }
 
 
     //Фабрика клиентских сертификатов
     KeyManagerFactory mgrFact = null;
     try {
  mgrFact = KeyManagerFactory.getInstance("X509");
     } catch (NoSuchAlgorithmException e1) {
  e1.printStackTrace();
     }
 
     KeyStore clientStore = null;
     try {
  clientStore = KeyStore.getInstance("PKCS12");
     } catch (KeyStoreException e1) {
  e1.printStackTrace();
     }
 
     try {
      clientStore.load(in, password.toCharArray());
     } catch (NoSuchAlgorithmException e1) {
      e1.printStackTrace();
     } catch (CertificateException e1) {
      e1.printStackTrace();
     } catch (IOException e1) {
      e1.printStackTrace();
     }
 
     try {
  mgrFact.init(clientStore, password.toCharArray());
     } catch (UnrecoverableKeyException e1) {
  e1.printStackTrace();
     } catch (KeyStoreException e1) {
  e1.printStackTrace();
     } catch (NoSuchAlgorithmException e1) {
  e1.printStackTrace();
     }
 
            //Фабрика управления доверительными соединениями
     //она будет использоваться для проверки сертифкатов 
            //всех https соединений 
     final TrustManager[] trustAllCerts = new TrustManager[] {new     
                  X509TrustManager() {
         public X509Certificate[] getAcceptedIssuers() {
            System.out.println("getAcceptedIssuers");
            return null;
         }
 
         public void checkServerTrusted(X509Certificate[] chain, String authType)
             throws CertificateException {
             System.out.println("Сведения о сертификате : " + 
                    chain[0].getIssuerX500Principal().getName() +
                    " Тип авторизации : " + authType);
         }
 
         public void checkClientTrusted(X509Certificate[] chain, String authType)
      throws CertificateException {
           System.out.println("checkClientTrusted : " + authType);
         }
     } };
 
 
     //Далее создаем sslContext
     SSLContext sslContext = null;
     try {
  sslContext = SSLContext.getInstance("TLS");
     } catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
     }
 
            //Настраиваем его с помощью наших фабрик
     try {
  sslContext.init(mgrFact.getKeyManagers(),trustAllCerts, new     
                java.security.SecureRandom());
     } catch (KeyManagementException e) {
  e.printStackTrace();
     }  
 
         final javax.net.ssl.SSLSocketFactory sslSocketFactory = 
                sslContext.getSocketFactory();
 
     //Создаем соединение
      HttpURLConnection conn = null;   
     try {
   conn = (HttpURLConnection) new URL(https_url).openConnection();
     } catch (MalformedURLException e) {
  e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
 
     //Настраиваем его
     try {
  conn.setRequestMethod("GET");
  conn.setUseCaches(false);
  conn.setDoInput(true);
  conn.setReadTimeout(10000 );
  conn.setConnectTimeout(15000);   
      } catch (ProtocolException e) {
  e.printStackTrace();
      }
 
      //encodeBase64 - функция преобразует строку в Base64
             //это если еще требуется авторизация
      conn.setRequestProperty("Authorization", "Basic  
                "+encodeBase64("user:password") ); 
 
             conn.setRequestProperty("Connection", "close");  
      conn.setRequestProperty("Accept-Charset", "cp1251"); 
      сonn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");  
      ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
 
      //Проверка имени хоста. Но проверку мы не делаем, а подтверждаем любой хост.
             //Кому надо, могут сделать проверку на что-нибудь
      ((HttpsURLConnection) conn).setHostnameVerifier(new HostnameVerifier() {
   public boolean verify(String arg0, SSLSession arg1) {
      return true;
  }
       });  
 
       // Устанавливаем соединение
       try {
  if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
   System.out.println("Ошибка соединения"+ conn.getResponseCode());
  }else{
   System.out.println("Соединение состоялось "+conn.getResponseCode()+
                        " ["+conn.getResponseMessage()+"]");
 
   StringBuffer buf = new StringBuffer();
   InputStreamReader in = new 
                             InputStreamReader(conn.getInputStream(), "cp1251");
   int ch;
   while ((ch = in.read()) != -1) {
    buf.append((char)c);
   }
   in.close();
 
   String response = buf.toString();
                 System.out.println("response "+response);
   //вывод различной информации по сертифкату, если надо.
   System.out.println("Cipher Suite : " + conn.getCipherSuite());
   Certificate[] certs = conn.getServerCertificates();
   System.out.println("n");
   for(Certificate cert : certs){
       System.out.println("Cert Type : " + cert.getType());
       System.out.println("Cert Hash Code : " + cert.hashCode());
       System.out.println("Cert Public Key Algorithm : " + 
                               cert.getPublicKey().getAlgorithm());
       System.out.println("Cert Public Key Format : " + 
                               cert.getPublicKey().getFormat());
   }    
  }
       } catch (IOException e) {
  e.printStackTrace();
       } catch (Exception e) {
  e.printStackTrace();
       }  
 }

 Это написано все в обработчике кнопки. Но желательно вынести все в отдельные функции или даже класс, что я реализую для себя сегодня )
 Спасибо Дмитрию и obrazel за их помощь, а так же моим коллегам.
Полезные статьи:
http://my-it-notes.com/2011/09/how-to-approve-ssl-certificate-on-android/ ,
http://www.cyberforum.ru/android-dev/thread613608-page2.html ,
http://www.javadocexamples.com/java/security/KeyStore/getInstance%28String%20type%29.html - вот эта полезная статья, которая наконец-то пнула меня в верном направлении и дала завершить проект!
 Если будут какие-то замечания по коду или предложения - буду рада услышать и узнать что-то новенькое!