레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시

수요일, 10월 27, 2010

[Android] My Tip; RemoteService Example

RemoteService Example

Reference Source:
http://developer.android.com/guide/developing/tools/aidl.html
http://developer.android.com/reference/android/app/Service.html
http://www.androidpub.com/102370



// -----------------------------------------------
// more Activity
// AndroidManifest.xml
// -----------------------------------------------
<activity android:name=".MainActivity"
android:label="MainActivity"
android:configChanges="keyboardHidden|orientation"
android:theme="@android:style/Theme.NoTitleBar"
android:launchMode="singleTask"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".anotherActivity"
android:label="anotherActivity"
android:configChanges="keyboardHidden|orientation"
android:theme="@android:style/Theme.NoTitleBar"
android:launchMode="singleTask"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

// -----------------------------------------------
// RemoteService
// AndroidManifest.xml
// -----------------------------------------------
<service android:enabled="true" android:name=".testActivity">
</service>


// -----------------------------------------------
// RemoteService
// AndroidManifest.xml
// -----------------------------------------------


// -----------------------------------------------
// RemoteService
// Service.java
// -----------------------------------------------
package com.test.testActivity;

// Service
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;

import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.MediaController;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

/*
@Activity A (test)
startService( new Intent(test.testActivity, testService.class).putExtra("tag", "data") );

String[] strList = {
{"strList1"},
{"strList2"}
};
startService( new Intent(test.testActivity, testService.class).putExtra("tag2", strlist) );

@Service B (testService)
Bundle extras = getIntent().getExtras();
String[] strList = extras.getStringArray( "strlist" );
*/
public class testService extends Service {
private String TAG = "testService";

// Service
private NotificationManager mNM;
private Intent m_InvokeIntent;
private volatile Looper m_ServiceLooper;
private volatile ServiceHandler m_ServiceHandler;
public String m_Str = null;

final RemoteCallbackList m_Callbacks
= new RemoteCallbackList();

private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
Bundle extras = (Bundle)msg.obj;
String strTagValue = null;

/*
{
// Broadcast
strTagValue = extras.getString( "command" );
if( strTagValue != null ) {
if( strTagValue.equals("pause") ) {
stopSelf( msg.arg1 );
return;
}
else if( strTagValue.equals("stop") ) {
stopSelf( msg.arg1 );
return;
}
}
}
*/

strTagValue = null;
strTagValue = extras.getString( "tag" );
Log.d( TAG, "Message: " + msg + ", tagValue = " + strTagValue );

if( strTagValue != null ) {
if( strTagValue.equals("data") ) {
}
else if( strTagValue.equals("strlist") ) {
m_strPlayList = extras.getStringArray( "strlist" );
}
else {
Log.d( TAG, "QUIT" );
stopSelf( msg.arg1 );
return;
}
}
else {
stopSelf( msg.arg1 );
return;
}
}
};
  
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService( NOTIFICATION_SERVICE );
HandlerThread thread = new HandlerThread( "MediaServiceStart" );
thread.start();

m_ServiceLooper = thread.getLooper();
m_ServiceHandler = new ServiceHandler( m_ServiceLooper );
}

@Override
public void onStart(Intent intent, int startId) {
Log.d( TAG, "ServiceStartArguments: Starting #" + startId + ": " + intent.getExtras() );
{
Message msg = m_ServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent.getExtras();
m_ServiceHandler.sendMessage( msg );
Log.d( TAG, "ServiceStartArguments: Sending: " + msg );

// Test: Broadcast
/*
final int n = m_Callbacks.beginBroadcast();
Log.d( TAG, "n = " + n );
for( int i = 0; i < n; i++ )
Log.d( TAG, "item = " + m_Callbacks.getBroadcastItem(i) );
m_Callbacks.finishBroadcast();
*/
}
}

@Override
public void onDestroy() {
m_ServiceLooper.quit();
}

@Override
public IBinder onBind(Intent intent) {
return m_Binder;
}

// The IRemoteInterface is defined through IDL
private final IRemoteService.Stub m_Binder = new IRemoteService.Stub() {
public void registerCallback(IRemoteServiceCallback cb) {
if (cb != null) m_Callbacks.register(cb);
}

public void unregisterCallback(IRemoteServiceCallback cb) {
if (cb != null) m_Callbacks.unregister(cb);
}

public String getInfo() {
String str = "i'm service";
return str;
}
public String setInfo(String str) {
Log.d( TAG, "RECEIVED From Activity: " + str );
return str;
}
};
}


// -----------------------------------------------
// testActivity.java
// -----------------------------------------------
// Service
import android.content.ServiceConnection;
import android.content.ComponentName;
import android.os.IBinder;
import android.os.RemoteException;
public class testActivity extends Activity implements {
// Service
private IRemoteService m_Service = null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//setContentView(R.layout.main);
//Intent(testActivity.this, testService.class)
//Intent intt = new Intent( testActivity.this, Activity2.class );
//startActivity( intt );

//bindService( new Intent(testService.class.getName()), m_Connection, Context.BIND_AUTO_CREATE );
bindService( new Intent(testActivity.this, testService.class), m_Connection, Context.BIND_AUTO_CREATE );
startService( new Intent(testActivity.this, testService.class).putExtra("tag", "quit") );

/*
if( m_Service != null ) {
try {
m_Service.unregisterCallback( m_Callback );
} catch (RemoteException e) {
}
}

// Detach our existing connection.
unbindService( m_Connection );
stopService( new Intent(testActivity.this, testService.class) );
*/
}

@Override
protected void onDestroy() {
if( m_Service != null ) {
try {
m_Service.unregisterCallback( m_Callback );
} catch (RemoteException e) {
}
}

if( m_Connection != null ) {
// Detach our existing connection.
unbindService( m_Connection );
}
stopService( new Intent(testActivity.this, testService.class) );

super.onDestroy();
}

// Service
private ServiceConnection m_Connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
m_Service = IRemoteService.Stub.asInterface(service);
try {
m_Service.registerCallback(m_Callback);
Log.d( TAG, "RECEIVED From Service: " + m_Service.getInfo() );
m_Service.setInfo( "i'm activity" );
} catch (RemoteException e) {
}
Toast.makeText( testActivity.this, "Service Connected", Toast.LENGTH_SHORT ).show();
}

public void onServiceDisconnected(ComponentName className) {
m_Service = null;
Toast.makeText( testActivity.this, "Service Disconnected", Toast.LENGTH_SHORT ).show();
}
};

// This implementation is used to receive callbacks from the remote service.
private IRemoteServiceCallback m_Callback = new IRemoteServiceCallback.Stub() {
public void valueChanged(int value) {
//mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
}
};
}

// -----------------------------------------------
// IRemoteService.aidl (default + method)
// Source (v1.6): C:/android-sdk-windows/platforms/android-4/samples/ApiDemos/src/com/example/android/apis/app
// -----------------------------------------------
package com.test.testActivity;
import com.test.testActivity.IRemoteServiceCallback;
interface IRemoteService {
void registerCallback(IRemoteServiceCallback cb);
void unregisterCallback(IRemoteServiceCallback cb);
// ADD
String getInfo();
String setInfo(String str);
}

// -----------------------------------------------
// IRemoteServiceCallback.aidl (default)
// Source (v1.6): C:/android-sdk-windows/platforms/android-4/samples/ApiDemos/src/com/example/android/apis/app
// -----------------------------------------------
default



-----
Cheers,
June

월요일, 10월 04, 2010

[Android] My Tip; Android Resolution Problem

Android Resolution Problem

Your App resolution wil be fixed 320x480(Portrait) if you missed the minSdkVersion.
so, you should be set it to 4(Android SDK Version 1.6) or higher.

Adds this into your AndroidManifest.xml:
Note: Change "(" to "<" and ")" to ">"
(manifest)
...
(uses-sdk android:minSdkVersion="4")(/uses-sdk)
(/manifest)

I've tested on Samsung Galaxy Tab.



-----
Cheers,
June

수요일, 9월 29, 2010

[Android] My Tip; Android ant release error (-dex: apply)

[Android] My Tip; Android ant release error (-dex: apply)


// ----------------------------------
// :: Preset
// ----------------------------------
 - Source Path
    C:/Program Files/MyAPKTest
 - Android SDK Path
    C:/android/android-sdk-windows
 - Using Android SDK Version: 2.1
    C:/android/android-sdk-windows/platforms/android-6/

// ----------------------------------
// :: Problem
//
// - Caused by this into dx.bat
//    if "%params%"=="" goto endArgs
//
// - e.g.,
//    set params = "--dex C:/Program Files/MyAPKTest/bin/classes.dex" "..." "..." "..."
//    if "%params%" == ""
// - DOS Batch Interpreter does not parse duplicated double-quotation.
// ----------------------------------
C:/Program Files/MyAPKTest> ant clean [OK]
C:/Program Files/MyAPKTest> ant compile [OK]
C:/Program Files/MyAPKTest> ant release
...
-dex:
     [echo] Converting compiled files and external libraries into C:/Program Files/MyAPKTest/bin/classes.dex...
     [echo]
    [apply] =C:/Files""은(는) 예상되지 않았습니다. (Korean)
    or
    [apply] =C:/Files"" was unexpected at this time. (English)
C:/Program Files/My APK Test>



// ----------------------------------
// :: Solution
// - Condition: Does not needs options
// ----------------------------------

// ----------------------------------
// 1. Open a dx.bat
// ----------------------------------
C:/android/android-sdk-windows/platforms/android-6/tools/dx.bat

// ----------------------------------
// 2. and Adds this in the above the ":nextArg"
// ----------------------------------
...
echo ***** SKIP check the parameter *****
set args=%params%
goto endArgs

:nextArg
if "%params%"=="" goto endArgs
...

// ----------------------------------
// 3. That's it, so now run clean & compile & release
// ----------------------------------
C:/Program Files/MyAPKTest> ant clean && ant compile && ant release
...
BUILD SUCCESSFUL
Total time: x seconds
C:/Program Files/MyAPKTest>



-----
Cheers,
June

수요일, 9월 15, 2010

[Android] My Tip; Android and ProGuard

Android and ProGuard
How to set ProGuard to ANT script for Android

STEP #1
Create a build.xml for ANT
c:/> ant update project --path c:/my_src
Create a build.property for keystore

STEP #2
EDIT build.xml

STEP #3
SETS ProGuard

STEP #4
BUILD

STEP #5
TEST, that's it!



//
// build.property
//
#application-package=com.test.testApp
#sdk-location=C:/android/android-sdk-windows/platforms/android-6

# KeyStore/Alias
key.store=__MY_KEYSTORE__.keystore
key.alias=__MY_KETSTORE_ALIAS__
key.store.password=__PASSWORD__
key.alias.password=__PASSWORD__

//
// build.xml
//
// --------------------------------
// 1. replace <setup />
// --------------------------------
by
    <!-- <setup /> -->
    <setup import="false" />

// --------------------------------
// 2. Adds this in the below <setup import="false" />
// --------------------------------
<!-- ProGuard -->
    <property name="proguard-dir" value="proguard"/>
    <property name="unoptimized" value="${proguard-dir}/unoptimized.jar"/>
    <property name="optimized" value="${proguard-dir}/optimized.jar"/>

    <target name="optimize" unless="nooptimize">
        <jar basedir="${out.classes.dir}" destfile="${unoptimized}"/>

        <java jar="${proguard-dir}/proguard.jar" fork="true" failonerror="true">
            <jvmarg value="-Dmaximum.inlined.code.length=16"/>
            <arg value="@${proguard-dir}/config.txt"/>     
            <arg value="-injars ${unoptimized}"/>
            <arg value="-outjars ${optimized}"/>
            <!--<arg value="-libraryjars ${android.jar}"/>-->
<!-- ADDS Library -->
            <arg value="-libraryjars ${android.jar}" />
            <!-- <arg value="-libraryjars libs/xxx.jar; libs/yyy.jar"/> -->
<!-- ADDS Library -->
        </java>    

        <!-- Delete source pre-optimized jar -->    
        <!--delete file="${unoptimized}"/-->

        <!-- Unzip target optimization jar to original output, and delete optimized.jar -->
        <delete dir="${out.classes.dir}"/>
        <mkdir dir="${out.classes.dir}"/>
        <unzip src="${proguard-dir}/optimized.jar" dest="${out.classes.dir}"/>

        <!-- Delete optimized jar (now unzipped into bin directory) -->
        <delete file="optimized.jar"/>
    </target>
<!-- ProGuard -->
...
<!-- 3. Copy rule and paste into build.xml; see below -->
...
</project>

// --------------------------------
// 3. Copy rule and paste into build.xml
// --------------------------------
OPEN a rule xml file
<SDK_PATH>/platforms/<target_platform>/templates/android_rules.xml

COPY from in the below <project name="android_rules" default="debug">
to in the above </project>
then, PASTE this in the below ProGuard

// --------------------------------
// 4. Finds <target name="-dex" depends="compile">
// --------------------------------
then, adds ", optimize" beside "compile"

<!-- ProGuard -->
    <target name="-dex" depends="compile, optimize">
<!-- ProGuard -->
...



//
// ProGuard
//
// --------------------------------
// 1. Get latest version
// --------------------------------
Download: http://sourceforge.net/project/showfiles.php?group_id=54750

// --------------------------------
// 2. Copy proguard.jar where into downloaded zip archive to my project directory
// --------------------------------
create a directory "proguard" into my project source directory
then, copy proguard/lib/proguard.jar to c:/my_src/proguard

// --------------------------------
// 3. Create a config.txt into "my_src_dir/proguard"
// --------------------------------
# --------------------------------------------------
# ProGuard:
#    http://proguard.sourceforge.net/
# ProGuard Download:
#    http://sourceforge.net/project/showfiles.php?group_id=54750
#
# config.txt Source:
#    http://www.androidengineer.com/2010/07/optimizing-obfuscating-and-shrinking.html
# --------------------------------------------------

-target 1.6
-optimizationpasses 2
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
#-verbose
#-dump class_files.txt
#-printseeds seeds.txt
#-printusage unused.txt
#-printmapping mapping.txt

# The -optimizations option disables some arithmetic simplifications that Dalvik 1.0 and 1.5 can't handle.
-optimizations !code/simplification/arithmetic

-keep public class * extends android.app.Activity
#-keep public class * extends android.app.Application
#-keep public class * extends android.app.Service
#-keep public class * extends android.content.BroadcastReceiver
#-keep public class * extends android.content.ContentProvider

#-keep public class * extends View {
#public <init>(android.content.Context);
#public <init>(android.content.Context, android.util.AttributeSet);
#public <init>(android.content.Context, android.util.AttributeSet, int);
#public void set*(...);
#}
# --------------------------------------------------

// --------------------------------
// build
// --------------------------------
c:/my_src> ant compile
c:/my_src> ant release


-----
Cheers,
June

목요일, 9월 02, 2010

[Android] My Tip; How to increase Android Emulator's Internal Phone Storage and RAM ?

How to increase Android Emulator's Internal Phone Storage and RAM ?

Usage:
-partition-size [Sizeo of the Internal Phone Storage; MB]
-qemu -m [Sizeo of the Physical RAM; MB]

Run:
- Internal Phone Storage: 512MB
- Physical RAM: 128MB (default)
C:/android-sdk-windows/tools> emulator -partition-size 512 -avd MyAVDname -qemu -m 128

-----
Cheers,
June

목요일, 8월 19, 2010

[Android] Android

Android

* Android Open Source Project
Source: http://android.git.kernel.org/

* Android x86 Project (Porting Android to x86)
Source: http://www.android-x86.org/


* Content Provider
Reference: http://www.devx.com/wireless/Article/41133


{ // CContentProvider Example
http://code.google.com/p/listenmystory/source/browse/

//
// CContentProvider Example
//

#DB: DB_CPtest
#TABLE NAME: info
#TABLE DESCRIPTION:
_id | title | data | data2 (extra)

// Get all of data
Uri allTitles = Uri.parse( "content://com.android.myTest1/db_cptest" );
Cursor c = managedQuery( allTitles, null, null, null, "_id asc" );
if( c.moveToFirst() ) {
do {
//Toast.makeText(this,
// c.getString(c.getColumnIndex(CContentProvider._ID)) + ", " +
// c.getString(c.getColumnIndex(CContentProvider.TITLE)) + ", " +
// c.getString(c.getColumnIndex(CContentProvider.DATA)) + ", " +
// c.getString(c.getColumnIndex(CContentProvider.DATA2)),
//Toast.LENGTH_LONG).show();

String strId = c.getString( c.getColumnIndex(CContentProvider._ID) );
String strTitle = c.getString( c.getColumnIndex(CContentProvider.TITLE) );
String strData = c.getString( c.getColumnIndex(CContentProvider.DATA) );
String strData2 = c.getString( c.getColumnIndex(CContentProvider.DATA2) );
Log.d( TAG, "id = " + strId + ", title = " + strTitle + ", data = " + strData + ", data2 = " + strData2 );
} while( c.moveToNext() );
}

// INSERT
ContentValues values = new ContentValues();
values.put( "title", "title1" );
values.put( "data", "data" );
values.put( "data2", "extra" );
Uri uri = Uri.parse( "content://com.android.myTest1/db_cptest" );
Uri resUri = getContentResolver().insert( uri, values );

// UPDATE #1
ContentValues values = new ContentValues();
values.put( "data", "data" );
Uri uri = Uri.parse( "content://com.android.myTest1/db_cptest/1" );
int resRowCount = getContentResolver().update( uri, values, null, null );
if( resRowCount > 0 )
Log.d( TAG, "Updated [TRUE]" );
else
Log.d( TAG, "Updated [FALSE]" );

// UPDATE #2
ContentValues values = new ContentValues();
values.put( "data", "data" );
Uri uri = Uri.parse( "content://com.android.myTest1/db_cptest" );
String[] strParam = { "title1" };
int resRowCount = getContentResolver().update( uri, values, "title=?", strParam );
if( resRowCount > 0 )
Log.d( TAG, "Updated [TRUE]" );
else
Log.d( TAG, "Updated [FALSE]" );
} // end of CContentProvider Example

// -----------------------------------------------------------



-----
Cheers,
June

목요일, 8월 12, 2010

[Android] Android Coverflow Widget

Android Coverflow Widget
http://www.inter-fuser.com/2010/01/android-coverflow-widget.html

Android Coverflow Widget V2
http://www.inter-fuser.com/2010/02/android-coverflow-widget-v2.html















Android Coverflow Widget V2
It's been a while now and I've been pretty busy with life, work, and other things, but at last here's a new version of the coverflow widget that I released a while back. There's not really much new functionality here, but I have cut down the amount of code needed by a lot. Gone are the CoverAbsSpinner and the CoverAdapterView classes and now we are left with just the CoverFlow class weighing in at less than 200 lines of code. Of course, you still have to instantiate it and populate it using an ImageAdapter so I'll also include the activity class for doing those tasks as well.

Background
The basic idea is that the Coverflow widget works very much like the standard Android Gallery widget, but with the addition of the rotation of the images. For more background information on the Coverflow widget see my original post here.

How to use
In your Android project create a package called com.example.coverflow, and in this place the CoverFlow class. Now all you need is an activity to instantiate the Coverflow widget in this example the activity class is called CoverflowExample. CoverFlowExample simply extends Activity and instantiates a Coverflow class in it's onCreate method. It also has an internal Class, ImageAdapter which we use as the adapter for the Coverflow widget, much as we would for a Gallery widget. It is also possible to use the Coverflow widget when it is specified in an XML layout file.

The CoverFlow and CoverflowExample classes are here:


/*
 * Copyright (C) 2010 Neil Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This code is base on the Android Gallery widget and was Created
 * by Neil Davies neild001 'at' gmail dot com to be a Coverflow widget
 *
 * @author Neil Davies
 */
package com.example.coverflow;


import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.Gallery;
import android.widget.ImageView;

public class CoverFlow extends Gallery {

    /**
     * Graphics Camera used for transforming the matrix of ImageViews
     */
    private Camera mCamera = new Camera();

    /**
     * The maximum angle the Child ImageView will be rotated by
     */  
    private int mMaxRotationAngle = 60;
  
    /**
     * The maximum zoom on the centre Child
     */
    private int mMaxZoom = -120;
  
    /**
     * The Centre of the Coverflow
     */
    private int mCoveflowCenter;

 public CoverFlow(Context context) {
  super(context);
  this.setStaticTransformationsEnabled(true);
 }

 public CoverFlow(Context context, AttributeSet attrs) {
  super(context, attrs);
        this.setStaticTransformationsEnabled(true);
 }

  public CoverFlow(Context context, AttributeSet attrs, int defStyle) {
   super(context, attrs, defStyle);
   this.setStaticTransformationsEnabled(true);
  }

    /**
     * Get the max rotational angle of the image
  * @return the mMaxRotationAngle
  */
 public int getMaxRotationAngle() {
  return mMaxRotationAngle;
 }

 /**
  * Set the max rotational angle of each image
  * @param maxRotationAngle the mMaxRotationAngle to set
  */
 public void setMaxRotationAngle(int maxRotationAngle) {
  mMaxRotationAngle = maxRotationAngle;
 }

 /**
  * Get the Max zoom of the centre image
  * @return the mMaxZoom
  */
 public int getMaxZoom() {
  return mMaxZoom;
 }

 /**
  * Set the max zoom of the centre image
  * @param maxZoom the mMaxZoom to set
  */
 public void setMaxZoom(int maxZoom) {
  mMaxZoom = maxZoom;
 }

 /**
     * Get the Centre of the Coverflow
     * @return The centre of this Coverflow.
     */
    private int getCenterOfCoverflow() {
        return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
    }
  
    /**
     * Get the Centre of the View
     * @return The centre of the given view.
     */
    private static int getCenterOfView(View view) {
        return view.getLeft() + view.getWidth() / 2;
    }
    /**
  * {@inheritDoc}
  *
  * @see #setStaticTransformationsEnabled(boolean)
  */
    protected boolean getChildStaticTransformation(View child, Transformation t) {

  final int childCenter = getCenterOfView(child);
  final int childWidth = child.getWidth() ;
  int rotationAngle = 0;

  t.clear();
  t.setTransformationType(Transformation.TYPE_MATRIX);

        if (childCenter == mCoveflowCenter) {
            transformImageBitmap((ImageView) child, t, 0);
        } else {    
            rotationAngle = (int) (((float) (mCoveflowCenter - childCenter)/ childWidth) *  mMaxRotationAngle);
            if (Math.abs(rotationAngle) > mMaxRotationAngle) {
             rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle : mMaxRotationAngle;
            }
            transformImageBitmap((ImageView) child, t, rotationAngle);      
        }  
          
  return true;
 }

 /**
  * This is called during layout when the size of this view has changed. If
  * you were just added to the view hierarchy, you're called with the old
  * values of 0.
  *
  * @param w Current width of this view.
  * @param h Current height of this view.
  * @param oldw Old width of this view.
  * @param oldh Old height of this view.
     */
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
      mCoveflowCenter = getCenterOfCoverflow();
      super.onSizeChanged(w, h, oldw, oldh);
     }

     /**
      * Transform the Image Bitmap by the Angle passed
      *
      * @param imageView ImageView the ImageView whose bitmap we want to rotate
      * @param t transformation
      * @param rotationAngle the Angle by which to rotate the Bitmap
      */
     private void transformImageBitmap(ImageView child, Transformation t, int rotationAngle) {          
      mCamera.save();
      final Matrix imageMatrix = t.getMatrix();;
      final int imageHeight = child.getLayoutParams().height;;
      final int imageWidth = child.getLayoutParams().width;
      final int rotation = Math.abs(rotationAngle);
                  
      mCamera.translate(0.0f, 0.0f, 100.0f);
      
      //As the angle of the view gets less, zoom in  
      if ( rotation < mMaxRotationAngle ) {
       float zoomAmount = (float) (mMaxZoom +  (rotation * 1.5));
       mCamera.translate(0.0f, 0.0f, zoomAmount);        
      }
    
      mCamera.rotateY(rotationAngle);
      mCamera.getMatrix(imageMatrix);            
      imageMatrix.preTranslate(-(imageWidth/2), -(imageHeight/2));
      imageMatrix.postTranslate((imageWidth/2), (imageHeight/2));
      mCamera.restore();
 }
}

// -----------------------------------------

/*
 * Copyright (C) 2010 Neil Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This code is base on the Android Gallery widget and was Created
 * by Neil Davies neild001 'at' gmail dot com to be a Coverflow widget
 *
 * @author Neil Davies
 */
package com.example.coverflow;

import java.io.FileInputStream;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

public class CoverFlowExample extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
  
     CoverFlow coverFlow;
     coverFlow = new CoverFlow(this);
  
     coverFlow.setAdapter(new ImageAdapter(this));

     ImageAdapter coverImageAdapter =  new ImageAdapter(this);
  
     //coverImageAdapter.createReflectedImages();
  
     coverFlow.setAdapter(coverImageAdapter);
  
     coverFlow.setSpacing(-25);
     coverFlow.setSelection(4, true);
     coverFlow.setAnimationDuration(1000);
  
  
     setContentView(coverFlow);
    }
  
 public class ImageAdapter extends BaseAdapter {
     int mGalleryItemBackground;
     private Context mContext;

     private FileInputStream fis;
      
     private Integer[] mImageIds = {
       R.drawable.kasabian_kasabian,
             R.drawable.starssailor_silence_is_easy,
             R.drawable.killers_day_and_age,
             R.drawable.garbage_bleed_like_me,
             R.drawable.death_cub_for_cutie_the_photo_album,
             R.drawable.kasabian_kasabian,
             R.drawable.massive_attack_collected,
             R.drawable.muse_the_resistance,
             R.drawable.starssailor_silence_is_easy
     };

     private ImageView[] mImages;
  
     public ImageAdapter(Context c) {
      mContext = c;
      mImages = new ImageView[mImageIds.length];
     }
  public boolean createReflectedImages() {
          //The gap we want between the reflection and the original image
          final int reflectionGap = 4;
        
        
          int index = 0;
          for (int imageId : mImageIds) {
        Bitmap originalImage = BitmapFactory.decodeResource(getResources(),
          imageId);
           int width = originalImage.getWidth();
           int height = originalImage.getHeight();
        
  
           //This will not scale but will flip on the Y axis
           Matrix matrix = new Matrix();
           matrix.preScale(1, -1);
        
           //Create a Bitmap with the flip matrix applied to it.
           //We only want the bottom half of the image
           Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height/2, width, height/2, matrix, false);
        
            
           //Create a new bitmap with same width but taller to fit reflection
           Bitmap bitmapWithReflection = Bitmap.createBitmap(width
             , (height + height/2), Config.ARGB_8888);
      
          //Create a new Canvas with the bitmap that's big enough for
          //the image plus gap plus reflection
          Canvas canvas = new Canvas(bitmapWithReflection);
          //Draw in the original image
          canvas.drawBitmap(originalImage, 0, 0, null);
          //Draw in the gap
          Paint deafaultPaint = new Paint();
          canvas.drawRect(0, height, width, height + reflectionGap, deafaultPaint);
          //Draw in the reflection
          canvas.drawBitmap(reflectionImage,0, height + reflectionGap, null);
        
          //Create a shader that is a linear gradient that covers the reflection
          Paint paint = new Paint();
          LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0,
            bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff,
            TileMode.CLAMP);
          //Set the paint to use this shader (linear gradient)
          paint.setShader(shader);
          //Set the Transfer mode to be porter duff and destination in
          paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
          //Draw a rectangle using the paint with our linear gradient
          canvas.drawRect(0, height, width,
            bitmapWithReflection.getHeight() + reflectionGap, paint);
        
          ImageView imageView = new ImageView(mContext);
          imageView.setImageBitmap(bitmapWithReflection);
          imageView.setLayoutParams(new CoverFlow.LayoutParams(120, 180));
          imageView.setScaleType(ScaleType.MATRIX);
          mImages[index++] = imageView;
        
          }
       return true;
  }

     public int getCount() {
         return mImageIds.length;
     }

     public Object getItem(int position) {
         return position;
     }

     public long getItemId(int position) {
         return position;
     }

     public View getView(int position, View convertView, ViewGroup parent) {

      //Use this code if you want to load from resources
         ImageView i = new ImageView(mContext);
         i.setImageResource(mImageIds[position]);
         i.setLayoutParams(new CoverFlow.LayoutParams(130, 130));
         i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
      
         //Make sure we set anti-aliasing otherwise we get jaggies
         BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
         drawable.setAntiAlias(true);
         return i;
    
      //return mImages[position];
     }
   /** Returns the size (0.0f to 1.0f) of the views
      * depending on the 'offset' to the center. */
      public float getScale(boolean focused, int offset) {
        /* Formula: 1 / (2 ^ offset) */
          return Math.max(0, 1.0f / (float)Math.pow(2, Math.abs(offset)));
      }

 }
}


Code explanation
The CoverflowExample Code contains some extra code for creating reflections of the images, but apart from this is a standard activity class of the kind used with the Gallery widget.

The CoverFlow class extends the Gallery widget , but now we override a few methods to allow us to transform the images before they are displayed. The most important method that we override is the getChildStaticTransformation method. Here I have to say thank you to a user called Nerdrow on the Android developers group who first showed me an example of overriding this method.  By setting setStaticTransformationsEnabled in the constructors, we are telling the parent ViewGroup of the Coverflow class to invoke getChildStaticTransformation every time one of our images in drawn. In getChildStaticTransformation we simply calculate and apply a rotation and scale to the ImageView depending on it's position relative to the centre of the Coverflow widget. We also override the onSizeChanged method. This method is called every time the widget changes size e.g. when we change the orientation of the phone from portrait to landscape. In the onSizeChanged method we just get the centre position of the coverflow widget. There are also a few parameters that we can set, mZoomMax controls the maximum zoom of the central image and mMaxRotationAngle sets the maximum rotation angle of each image.

Thanks
Lastly, I just wanted to say thanks to everyone who has commented and given me feedback on the coverflow widget. It's be really positive and great to hear that people have got it working successfully on a range of devices such as the HTC magic , Motorolla droid and Nexus 1. There's still more to do for the coverflow widget and I'll release new version as and when I make updates. If anyone has anymore suggests for cool widgets or enhancements to this Coverflow widget then please do get in touch.


-----
Great work!

Cheers,
June

수요일, 8월 11, 2010

[Android] MediaPlayer Time Format

MediaPlayer Time Format

// See below site
http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob_plain;f=core/java/android/widget/MediaController.java;hb=71beeab14a891297b6658c4aeb47557083091aa1

int position = mediaPlayer.getCurrentPosition();
int duration = mediaPlayer.getDuration();

int timeMs = position;
or
int timeMs = duration;

int totalSeconds = timeMs / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours   = totalSeconds / 3600;

-----
Cheers,
June

금요일, 8월 06, 2010

[Android] Resize SurfaceView

Android Resize SurfaceView

:: in Activity Java Source Code
// Get SurfaceView LayoutParams info
android.view.ViewGroup.LayoutParams lp = m_SurfaceView.getLayoutParams();

// Set to params
lp.width = 320;
lp.height = 240;

// Apply to new dimension
m_SurfaceView.setLayoutParams( lp );
// Set left, top margin
int l = (getWindowManager().getDefaultDisplay().getWidth() - lp.width) / 2;
int t = (getWindowManager().getDefaultDisplay().getHeight() - lp.height) / 2;
((android.widget.RelativeLayout.LayoutParams)m_SurfaceView.getLayoutParams()).setMargins( l, t, 0, 0 );


// Restore to Full size
/*
lp.width = getWindowManager().getDefaultDisplay().getWidth();
lp.height = getWindowManager().getDefaultDisplay().getHeight();
m_SurfaceView.setLayoutParams( lp );
((android.widget.RelativeLayout.LayoutParams)m_SurfaceView.getLayoutParams()).setMargins( 0, 0, 0, 0 );
*/

-----
Cheers,
June

목요일, 8월 05, 2010

[Android] Show or Hide Panel layout on SurfaceView such as MediaController layout

Show or Hide Panel layout on SurfaceView such as MediaController layout

// NOTE: You have to simple layout that
// LinearLayout_MyPanel(layout_width="fill_parent", layout_height="50dp", ...) or whatever
// in order to applying below code.

// Show Panel layout on SurfaceView
private boolean ShowMyPanel(boolean isRunnable) {
Log.d( "FUNC", "ShowMyPanel()" );

boolean ret = true;
android.widget.LinearLayout rl = (android.widget.LinearLayout)findViewById( R.id.LinearLayout_MyPanel );
if( rl.getVisibility() != rl.VISIBLE ) {
if( isRunnable )
return false;

    rl.setVisibility( 0 );
    ret = true;
   
    Log.d( "LOG", "Show" );
}
else {
    rl.setVisibility( 4 );
    ret = false;
    Log.d( "LOG", "Hidden" );
}
//rl.invalidate();

return ret;
}

// SurfaceView Touch Event
// Show or Hide the Panel layout on SurfaceView after 3 seconds
View.OnClickListener surfaceViewListener = new View.OnClickListener() {
Handler handlerMyPanel = null;
@Override
public void onClick(View v) {
// TODO Auto-generated method stub

//Toast.makeText( myClass.this, "SurfaceView...", Toast.LENGTH_LONG ).show();
Log.d( "LOG", "SurfaceView Touch Event" );

/*
       android.widget.LinearLayout rl = (android.widget.LinearLayout)findViewById( R.id.LinearLayout_MyPanel );
       if( rl.getVisibility() != rl.VISIBLE )
         rl.setVisibility( 0 );
         else
        rl.setVisibility( 4 );
       //rl.invalidate();
       */
if( !ShowMyPanel(false) )
return;

if( handlerMyPanel != null ) {
Log.d( "LOG", "handlerMyPanel != null" );
handlerMyPanel.removeCallbacksAndMessages( null );
handlerMyPanel = null;
}
handlerMyPanel = new Handler();
handlerMyPanel.postDelayed( new Runnable() {
public void run() {
Log.d( "RUNNABLE", "MyPanel start" );
ShowMyPanel(true);
Log.d( "THREAD", "MyPanel finish" );
}
}, 3000
);
}
};
m_SurfaceView.setOnClickListener( surfaceViewListener );

-----
Cheers,
June

금요일, 7월 23, 2010

[Android] Lifecycle

Android Lifecycle of Activity

:: LANDSCAPE mode
[Incoming Call Event]
 -> public void onSaveInstanceState(Bundle savedInstanceState) {}
[Restore]
 -> public void onConfigurationChanged(Configuration newConfig) {}

:: Portrait mode
[Incoming Call Event]
 -> public void onSaveInstanceState(Bundle savedInstanceState) {}
[Restore]
 -> public void onRestoreInstanceState(Bundle savedInstanceState) {}


for Example,
 - Lifecycle of VideoView and SurfaceView (Landscape, Portrait)

:: VideoView(with MediaController; LANDSCAPE mode)
[Incoming Call Event]
 -> public void onSaveInstanceState(Bundle savedInstanceState) {}
[Restore]
 -> public void onConfigurationChanged(Configuration newConfig) {}

:: SurfaceView(with MediaPlayer; Portrait mode)
[Incoming Call Event]
 -> public void onSaveInstanceState(Bundle savedInstanceState) {}
[Restore]
 -> public void onRestoreInstanceState(Bundle savedInstanceState) {}

-----
Cheers,
June

수요일, 7월 21, 2010

[Android] My Tip - SeekBar

My Android Tip
 - Use at your own risk! lol, ㅋㅋㅋ


내가 작업한 것 중에서 유용한 Tip 들을 정리 해본다.

// ---------------------------------------------
// :: SeekBar
// ---------------------------------------------
 - main.xml
android:layout_width="fill_parent"
android:layout_height="wrap_contents"
android:clickable="true"
android:paddingLeft="5px"
android:paddingRight="5px"
android:maxHeight="20px" // this means ProgressiveBar Height
android:max="1000"
android:progress="0"
android:fitsSystemWindows="true"
android:thumb="@drawable/seekbar_thumb"
android:progressDrawable="@drawable/seekbar"
android:secondaryProgress="0">

 - seekbar.xml: Change "(" to "<"
(layer-list xmlns:android="http://schemas.android.com/apk/res/android")
    (item android:id="@android:id/background")
     (clip android:drawable="@drawable/bg" /)
    (/item)
/*
    (item android:id="@android:id/secondaryProgress")
        (clip android:drawable="@drawable/progress2" /)
(/item)
*/
    (item android:id="@android:id/progress")
(clip android:drawable="@drawable/progress" /)
(/item)
(/layer-list)

 - seekbar_thumb.xml: Change "(" to "<"
(selector xmlns:android="http://schemas.android.com/apk/res/android")
 (item android:state_pressed="true"
android:drawable="@drawable/thumb_button_pressed" /)
 (item android:state_focused="true"
android:drawable="@drawable/thumb_button_focused" /)
 (item android:state_focused="false"
android:drawable="@drawable/thumb_button_normal" /)
(/selector)


 - Thread:
Thread.sleep( 1000 );
if( m_MediaPlayer.isPlaying() == false )
break;
m_Handler.sendMessage( m_Handler.obtainMessage() );

 - SeekBar:
        // SeekBar
        m_SeekBar = (SeekBar)findViewById( R.id.SeekBar01 );
        m_SeekBar.setProgress( 0 );
        m_SeekBar.setClickable( true );

 - Handler:
m_Handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if( (m_MediaPlayer == null) ) {
m_SeekBar.setProgress( 0 );

// something
// ...

return;
}

if( m_MediaPlayer.isPlaying() ) {
int position = m_MediaPlayer.getCurrentPosition();
int duration = m_MediaPlayer.getDuration();
if( m_SeekBar != null ) {
if( duration > 0 ) {
long pos = m_MediaPlayer.getDuration() * position / duration;
m_SeekBar.setProgress( m_MediaPlayer.getCurrentPosition() );
}

// something
// ...

// NOTE: below code are just sample...
// ...

int m = (int) Math.floor(m_MediaPlayer.getCurrentPosition()/(1000*60));
int s = (int) Math.floor((m_MediaPlayer.getCurrentPosition()-(1000*60)*m)/1000);
m_CurrentSec = (m * 60) + s;
String strM = m < 10 ? "0" + Integer.toString( m ) : Integer.toString( m );
String strS = s < 10 ? "0" + Integer.toString( s ) : Integer.toString( s );
m_strCurrentMS = strM + ":" + strS;

Log.d( "THREAD", "Cur = " + m_CurrentSec + ", Duration = " + m_DurationSec );
if( ((m_CurrentSec+1) >= m_DurationSec) || ((m_CurrentSec+1) >= m_DurationSec) ) {
Log.d( "THREAD", "THREAD END..." );

m_strCurrentMS = "00:00";
if( m_MediaPlayer != null ) {
m_MediaPlayer.release();
m_MediaPlayer = null;
}
m_SeekBar.setProgress( 0 ); // reset

// something
// ...
}
}
}
else {
Log.d( "THREAD", "Playing == false" );
m_strCurrentMS = "00:00";
if( m_MediaPlayer != null ) {
m_MediaPlayer.release();
m_MediaPlayer = null;
}

m_SeekBar.setProgress( 0 ); // reset

// something
// ...
}
};
};

m_SeekBar.setOnSeekBarChangeListener( new OnSeekBarChangeListener() {
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub

   if( m_MediaPlayer != null )
m_CurrentPosition = m_MediaPlayer.getCurrentPosition();
   else
       m_SeekBar.setProgress( 0 );
}
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}

public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// TODO Auto-generated method stub
if( !fromUser )
return;

   if( m_MediaPlayer != null ) {
long duration = m_MediaPlayer.getDuration();
//long newposition = (duration * progress) / 1000L;
long newposition = (duration * progress) / m_MediaPlayer.getDuration();
m_MediaPlayer.seekTo( (int) newposition);
   }
}
}
);
// ---------------------------------------------



:: Disable SeekBar Touch Event
seekBar.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//return false;
return true;
}
});



-----
Cheers,
June

화요일, 7월 13, 2010

[Android] My Tip

My Android Tip
 - Use at your own risk! lol, ㅋㅋㅋ


내가 작업한 것 중에서 유용한 Tip 들을 정리 해본다.

// ---------------------------------------------
// :: AndroidManifest.xml
// ---------------------------------------------
* android:sharedUserId="..."
* for Froyo (Android 2.2 API Level 8 ~)
android:installLocation = {
"internalOnly",
"preferExternal",
"auto"
};
android:installLocation="auto"

* android:launchMode="singleTask"

* Prevent to restart Activity Life-Cycle when slide-out QWERTY keypad
* Landscape/Portrait mode event
android:screenOrientation="portrait"
android:configChanges="keyboardHidden|orientation"

* android:theme="@android:style/Theme.Translucent"
// ---------------------------------------------


// ---------------------------------------------
// :: Slide Strings
// ---------------------------------------------
    // NOTE: Slide Strings
android:ellipsize = {
"marquee" -> slide
"end" -> "..."
}
android:singleLine="true"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"

    // main.xml
    // ----------------------------------------------
    
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12.00sp"
android:gravity="left|center_vertical"
android:height="20.0dp"
android:width="300.0dp"
android:singleLine="true"
android:ellipsize="end"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="">
  


    // TextView Click Event
    // ----------------------------------------------
    // TextViewStr1
    View.OnClickListener TextViewStr1Listener = new View.OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub

Log.d( "LOG", "TextViewStr1Listener: focus = false" );
//m_TextViewStr_xxx.clearFocus();

m_TextViewStr1.setEllipsize( TruncateAt.MARQUEE );
m_TextViewStr1.setFocusable( true );
};
    };
    m_TextViewStr1.setOnClickListener( TextViewStr1Listener );
  
    // Focus
    View.OnFocusChangeListener focusTextViewStr1Listener = new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
// TODO Auto-generated method stub

if( hasFocus ) {
Log.d( "LOG", "[FOCUS] TextViewStr1Listener: focus = true" );
//m_TextViewStr_xxx.clearFocus();

m_TextViewStr1.setEllipsize( TruncateAt.MARQUEE );
m_TextViewStr1.setFocusable( true );
}
else {
Log.d( "LOG", "[FOCUS] TextViewStr1Listener: focus = false" );
m_TextViewStr1.setEllipsize( TruncateAt.END );
}
}
};
m_TextViewStr1.setOnFocusChangeListener( focusTextViewStr1Listener );
// ---------------------------------------------


// ---------------------------------------------
// :: ViewControl Visibility
// ---------------------------------------------
// VISIBLE: 0x00000000, INVISBLE: 0x00000004, GONE: 0x00000008
ImageView.setVisibility( 0 );
ImageView.invalidate();
// ---------------------------------------------    


// ---------------------------------------------
// :: Events
// ---------------------------------------------
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        // TODO Auto-generated method stub
        super.onConfigurationChanged(newConfig);
      
        Log.d( "FUNC", "onConfigurationChanged" );


        if( m_isFullScreen ) {
         Log.d( "LOG", "Full Screen -> restore" );
         m_isFullScreen = false;
         //getWindow().setFlags( WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR, WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR );
         //setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_PORTRAIT );
         getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN );
        }
        else {
         Log.d( "LOG", "Full Screen" );
         m_isFullScreen = true;
         getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN );
        }

    }

    /*
     * Activity Status(Activity Lifecycle)
run1: app
onStart -> onResume

run2-1: app -> no work -> home
onSaveInstanceState -> onStop
run2-2: app
onStop -> onRestart -> onStart -> onResume

run3-1: work -> home
onSaveInstanceState -> onStop
run3-2: app
onRestart -> onStart -> onResume
    */

Figure Source: http://developer.android.com/guide/topics/fundamentals.html


  
















// NOTE: Activity LifeCycle method 들의 한글 주석은 출처가 없다.

    // onCreate가 호출이 된 후 GUI 상태 복구를 위해서 사용을 하는 함수
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        // savedInstaceState로부터 GUI 상태를 복구를 한다.
        // 인수로 받는 Bundle은 onCreate에도 전달된다.
      
        Log.d( "FUNC", "onRestoreInstanceState" );


        if( !m_isPauseBySystem ) {
         Log.d( "PLAY", "keep state: pauseBySystem == false" );
         return;
        }
        if( m_MediaPlayer == null ) {
         Log.d( "LOG", "m_MediaPlayer == null" );
         return;
        }
        else {
         Log.d( "LOG", "now restart..." );
         m_MediaPlayer.start();
          }

    }
  
    /// visible lifetime으로 가기 전 activity 처리를 위해서 호출이 된다.
    public void onRestart(){
        super.onRestart();
        // activity가 이미 화면에 보이고
        // 여기서 변경된 것들만 읽어들인다.
      
        Log.d( "FUNC", "onRestart" );
    }

    /// visible lifetime start시에 호출된다.
    public void onStart(){
        super.onStart();
        // 여기서는 activity가 화면에 보이므로
        // 필요한 GUI 변경 사항을 적용한다.
      
        Log.d( "FUNC", "onStart" );
    }

    /// active lifetime시에 호출이 된다.
    public void onResume(){
        super.onResume();
        /// 일시 중지된 모든 GUI update나 thread
        /// activity에 의해서 필요하지만 activity가 비활성화
        /// 되면서 일시 중단된 것들을 처리를 다시 시작을 할 때
      
        Log.d( "FUNC", "onResume" );
      

     Intent intt = new Intent( "com.android.music.musicservicecommand" );
     intt.putExtra( "command", "pause" );
     sendBroadcast( intt );

        if( !m_isPauseBySystem ) {
         Log.d( "PLAY", "keep state: pauseBySystem == false" );
         return;
        }
        if( m_MediaPlayer == null ) {
         Log.d( "LOG", "m_MediaPlayer == null" );
         return;
        }
        else {
         Log.d( "LOG", "now restart..." );
         m_MediaPlayer.start();
        }


        // for Some Interrupt
// ...
    }

    /// GUI 상태를 저장을 하기 위해서 호출이된다.
    public void onSaveInstanceState(Bundle savedInstanceState){
        /// GUI state를 savedInstanceState에 저장한다.
        /// 프로세스가 중료되거나 재시작될 경우
        /// 이 번들이 onCreate에 전달이 되서
        /// 그 시점부터 다시 시작이 될 것이다.
        super.onSaveInstanceState(savedInstanceState);
      
        Log.d( "FUNC", "onSaveInstanceState" );


        if( m_MediaPlayer == null ) {
         Log.d( "LOG", "m_MediaPlayer == null" );
         return;
        }
        else {
         Log.d( "LOG", "now pause... by System" );
         m_isPauseBySystem = true;
         m_MediaPlayer.pause();
         // save m_MediaPlayer.getCurrentPosition()...
        }

      
        // for Some Interrupt
// ...
    }

    /// active lefetime 끝에서 호출된다.
    public void onPuase() {
        super.onPause();
        /// activity가 포그라운드 상태가 아닐 경우
        /// 다시 update 되거나 필요가 없는 GUI및 Thread
        /// 프로세서 점유율이 높이는 처리는 일시 중지한다
      
        Log.d( "FUNC", "onPuase" );


        if( m_MediaPlayer == null ) {
         Log.d( "LOG", "m_MediaPlayer == null" );
         return;
}
        else {
         m_isPauseBySystem = true;
         m_MediaPlayer.pause();
         // save m_MediaPlayer.getCurrentPosition()...
        }


      
        // Try adding android:configChanges="keyboardHidden|orientation"
        // to your AndroidManifest.xml. This tells the system what configuration changes
        // you are going to handle yourself
        // android:configChanges="keyboardHidden|orientation
          
        // for Some Interrupt
// ...
    }

    /// visible lifetime 끝에서 호출
    public void onStop() {
        super.onStop();
        /// 남아있는 GUI 업데이트나 쓰레드
        /// activity가 화면에 보이지 않을 때 필요치 않은 처리를 일시 중단을 한다.
        /// 이 메서드가 호출이 되고 난 뒤에는 프로세스가 종료될 가능성이 있으므로
        /// 바뀐 모든 내용과 상태 변화를 지속시킨다.
      
        Log.d( "FUNC", "onStop" );
    }
// ---------------------------------------------


// ---------------------------------------------
// :: Surface and Media Player
// ---------------------------------------------

public class TESTAPP extends Activity implements
OnPreparedListener,
SurfaceHolder.Callback {
...
}
...

    // SurfaceHolder
    public void surfaceChanged(SurfaceHolder surfaceholder, int format, int width, int height) {
     Log.d( "FUNC", "surfaceChanged()" );
    }
    public void surfaceDestroyed(SurfaceHolder surfaceholder) {
     Log.d( "FUNC", "surfaceDestroyed()" );


     if( m_MediaPlayer == null )
     return;
     else
     m_MediaPlayer.pause();

    }
    public void surfaceCreated(SurfaceHolder holder) {
     Log.d( "FUNC", "surfaceCreated()" );
    
// mediaPlayer not null ?
// then start to play or pause

    // NOTE: update surface
    //m_MediaPlayer.setDisplay( holder );

/*
     if( m_MediaPlayer == null ) {
     Log.d( "LOG", "m_MediaPlayer == null" );
return;
     }
     else {
     Log.d( "LOG", "now Playing..." );
m_MediaPlayer.setDisplay( holder );
//m_MediaPlayer.pause();
//m_MediaPlayer.start();
*/


     if( m_MediaPlayer == null ) {
     Log.d( "LOG", "null; playing..." );
return;
     }
     else {
     Log.d( "LOG", "playing..." );
/*
     if( m_MediaPlayer.getCurrentPosition() == 0 )
m_MediaPlayer.start();
     }
     else {
     Log.d( "LOG", "use the holder" );
     m_MediaPlayer.setDisplay( holder );
    
m_MediaPlayer.start();
*/
    m_SurfaceHolder = holder;
    // re-create MediaPlayer, reset SurfaceView, seekTo(...), ...
    // PlayVideo(); // see below code Video Player with SurfaceView
     }

}

    }
  
    public void onPrepared(MediaPlayer mediaplayer) {
        Log.d( "FUNC", "MediaPlayer: onPrepared()" );

// mediaPlayer not null ?
// then start to play

        if( m_MediaPlayer != null )
         m_MediaPlayer.start();
else
Log.d( "LOG", "m_MediaPlayer == null" );

    }
// ---------------------------------------------


// ---------------------------------------------
// :: Handler
// ---------------------------------------------
    private Handler m_Handler = null;

{
m_Handler = new Handler();
m_Handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// update something
// or
// skip
};
};
}

// another

if( m_Handler != null ) {
Log.d( "LOG", "m_Handler != null" );
m_Handler.removeCallbacksAndMessages( null );
m_Handler = null;
}

m_Handler = new Handler();
m_Handler.postDelayed( new Runnable() {
public void run() {
Log.d( "RUNNABLE", "start" );
// Something...
Log.d( "THREAD", "finish" );
}
}, 5000
);

or

m_Handler = new Handler() {
public void handleMessage(android.os.Message msg) {
Log.d( "FUNC", "HANDLER: handleMessage()" );

switch( msg.what ) {
case 0:
// Something
break;
}
};
};
// ---------------------------------------------


// ---------------------------------------------
// :: Thread Start and Stop
// ---------------------------------------------
    private void threadStop() {
     Log.d( "FUNC", "threadStop()" );

     m_ThreadRunFlag = false;
m_Thread = null;
    }
    private void threadStart() {
Log.d( "FUNC", "threadStart()" );
  
if( m_Thread != null ) {
     Log.d( "THREAD", "m_Thread != NULL" );
     return;
     }

     m_ThreadRunFlag = true;
m_Thread = new Thread() {
public void run() {
try {
// checks MediaPlayer Status
//

while( m_ThreadIsRunning == true ) {
Thread.sleep( 1000 );

// checks MediaPlayer Status
//

m_Handler.sendMessage( m_Handler.obtainMessage() );
}

m_ThreadRunFlag = false;
}
catch( Throwable t ) {
m_ThreadRunFlag = false;
}
}

if( m_Thread != null )
m_Thread.start();
else
Log.d( "THREAD", "m_Thread == NULL" );
    };
// ---------------------------------------------


// ---------------------------------------------
// :: MessageBox
// ---------------------------------------------
    private void dlgFail() {
final Builder winAlert;
Dialog winDialog;
     LayoutInflater li = LayoutInflater.from( this );
     View viewDlgFail = li.inflate( R.layout.dlgFail, null );

     DialogInterface.OnClickListener okListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub

// Button 1
if( which == -1 ) {
Log.d( "Button", "Selected Button 1" );
//Toast.makeText( __MyActivity__.this, "Selected Button 1", Toast.LENGTH_LONG ).show();
}
// Button 2
else if( which == -3 ) {
Log.d( "Button", "Selected Button 2" );
//Toast.makeText( __MyActivity__.this, "Selected Button 2", Toast.LENGTH_LONG ).show();
}
// Button 3
else if( which == -2 ) {
Log.d( "Button", "Selected Button 3" );
//Toast.makeText( __MyActivity__.this, "Selected Button 3", Toast.LENGTH_LONG ).show();
}

dialog.cancel();

// Quit
// System.exit( 1 );
return;
}
};

winAlert = new AlertDialog.Builder( this )
.setIcon( R.drawable.icon )
.setTitle( getResources().getString(R.string."__FAIL_MESSAGE_TITLE__") )
.setMessage( testGMP.this.m_strResMsg )
.setPositiveButton( getResources().getString(R.string."__FAIL_MESSAGE_BUTTON_OK__"), okListener )
.setView( viewDlgFail );
winDialog = winAlert.create();
winDialog.show();
    }
// ---------------------------------------------


// ---------------------------------------------
// :: Resource to Export
// ---------------------------------------------
// Get current mounted SD card path
String strFilePathExt = Environment.getExternalStorageDirectory().getPath();
String strFilePath = strFilePathExt + "/";
String strFilename = "_export_filename_";

// Get SD card State
Environment.getExternalStorageState()


// Checks Internal Storage Free Space
{
File path = Environment.getDataDirectory();
StatFs stat = new StatFs( path.getPath() );
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
{
AssetFileDescriptor afd = getResources().openRawResourceFd( R.raw.__RAW_MEDIA_FILE__ );
if( (availableBlocks * blockSize) < afd.getLength() ) {
Toast.makeText( this, getResources().getString(Out of Internal Storage), Toast.LENGTH_LONG ).show();
afd.close();
return;
}

afd.close();
}
}

// Checks Mount State and External Storage Free Space
if( android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED) ) {
File path = Environment.getExternalStorageDirectory();
StatFs stat = new StatFs( path.getPath() );
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
{
AssetFileDescriptor afd = getResources().openRawResourceFd( R.raw.__RAW_MEDIA_FILE__ );
if( (availableBlocks * blockSize) < afd.getLength() ) {
Toast.makeText( this, getResources().getString("Out of External Storage"), Toast.LENGTH_LONG ).show();
afd.close();
return;
}

afd.close();
}
}

// Resource file to Export to SD card
{
     AssetFileDescriptor afd = this.getResources().openRawResourceFd( R.raw.__RAW_MEDIA_FILE__ );
   
if( (new File(strFilePath + strFilename).exists()) == true ) {
Log.d( "EXPORT", "Exist = " + strFilePath + strFilename + ", " + "Internal Resource File Size = " + afd.getLength() + ", External(Exported) Resource File Size = " + (new File(strFilePath + strFilename).length()) );
if( (new File(strFilePath + strFilename).length()) == afd.getLength()) {
Toast.makeText( this, getResources().getString("File existed already"), Toast.LENGTH_LONG ).show();
afd.close();
return;
}
}

byte[] buf_readBytes = new byte[1024];
int readBytes = 0;
int offset = 0;
int count = 1024;

OutputStream oStream = new FileOutputStream( strFilePath + strFilename );

FileInputStream fis = afd.createInputStream();
while( true ) {
int bytes = fis.read( buf_readBytes );
if( bytes <= 0 ) break;
oStream.write( buf_readBytes, 0, bytes );
}

oStream.flush();
oStream.close();
fis.close();
afd.close();
}
// ---------------------------------------------


// ---------------------------------------------
// :: Use Cache Directory and File
// ---------------------------------------------
    private File m_TmpFile = null;

    ...

    @Override
    protected void onDestroy() {
     super.onDestroy();

     try {
if( m_TmpFile != null )
m_TmpFile.delete();
}
     catch( Exception e ) {
     Log.e( "FUNC", "onDestroy(): error: " + e.getMessage(), e );
     }
    }
{
try {
AssetFileDescriptor afd = getResources().openRawResourceFd( R.raw.__RAW_MEDIA_FILE__ );
FileInputStream fis = afd.createInputStream();
String strTmpFilename = "";

if( m_TmpFile != null )
m_TmpFile.delete();

Log.d( "CacheFile", "Full Resource Size = " + Long.toString(fis.getChannel().size()) );
Log.d( "CacheFile", "Current Resource Position = " + Long.toString(fis.getChannel().position()) );
Log.d( "CacheFile", "Current Resource Size = " + Long.toString(afd.getLength()) );

{
byte[] buf_readBytesFile = new byte[1024];
File cacheDir = getCacheDir();
String strFilename = "__CACHE_TMP_FILENAME__";
m_TmpFile = File.createTempFile( strFilename, ".tmp", cacheDir );
OutputStream oStream = new FileOutputStream( m_TmpFile );

Log.d( "CacheFile", "CacheTmpFilename = " + m_TmpFile.getPath() );

while( true ) {
int bytes = fis.read( buf_readBytesFile );
if( bytes <= 0 ) break;
oStream.write( buf_readBytesFile, 0, bytes );
}

oStream.flush();
strTmpFilename = m_TmpFile.getAbsolutePath();
oStream.close();
}

Log.d( "CacheFile", "CacheTmpFilename = " + strTmpFilename );

afd.close();
fis.close();
}
catch( Exception e ) {
// ...
}
}
// ---------------------------------------------


// ---------------------------------------------
// :: Play Video
// ---------------------------------------------
// See above code SurfaceView method
public class TESTAPP extends Activity implements
OnPreparedListener,
SurfaceHolder.Callback {
...


     Intent intt = new Intent( "com.android.music.musicservicecommand" );
     intt.putExtra( "command", "pause" );
     sendBroadcast( intt );

}
...

private MediaPlayer m_MediaPlayer = null;
private SurfaceView m_SurfaceView = null;
private SurfaceHolder m_SurfaceHolder = null;
// VideoView
m_VideoView = (VideoView)findViewById( R.id.VideoView01 );
// SurfaceView
m_SurfaceView = (SurfaceView)findViewById( R.id.SurfaceView01 );
m_SurfaceHolder = m_SurfaceView.getHolder();
m_SurfaceHolder.addCallback( this );
m_SurfaceHolder.setType( SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS );
m_SurfaceView.setClickable( false );

// Visibility
// VideoView
//m_VideoView.setVisibility( 0 );
//m_VideoView.invalidate();
// SurfaceView
m_SurfaceView.setVisibility( 0 );
m_SurfaceView.invalidate();

// Button: Play
Button btnPlay = (Button)findViewById( R.id.Button01 );
View.OnClickListener btnPlayListener = new View.OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub

if( m_MediaPlayer == null ) {
Log.d( "PLAY", "OK, First time" );
PlayVideo();
}
else {
PlayVideo();
}
}
};
btnPlay.setOnClickListener( btnPlayListener );

public void PlayVideo() {
if( m_MediaPlayer != null )
m_MediaPlayer.release();
m_MediaPlayer = null;
m_MediaPlayer = new MediaPlayer();
AssetFileDescriptor afd = getResources().openRawResourceFd( R.raw.__RAW_MEDIA_FILE__ );

if( m_MediaPlayer == null ) {
Log.d( "MediaPlayer", "m_MediaPlayer == NULL" );
return;
}

// Use ViewoView
/*
if( m_VideoView == null ) {
Log.d( "VideoView", "m_VideoView == NULL" );
return;
}
*/

// Use SurfaceView
if( m_SurfaceView == null ) {
Log.d( "SurfaceView", "m_SurfaceView == NULL" );
return;
}

m_MediaPlayer.setAudioStreamType( AudioManager.STREAM_MUSIC );
 m_MediaPlayer.setScreenOnWhilePlaying( true );
   try {
m_MediaPlayer.setDataSource( afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength() );

// Use VideoView
//m_VideoView.getHolder().addCallback( this );
//m_MediaPlayer.setDisplay( m_VideoView.getHolder() );

// Use Surface
m_MediaPlayer.setDisplay( m_SurfaceHolder );
m_MediaPlayer.prepare();  
   }
     catch( IllegalStateException e ) {
     // ...
     }
     catch( IOException e ) {
     // ...
     }

  if( m_CurrentPosition > 0 )
      m_MediaPlayer.seekTo( m_CurrentPosition );
  if( !isPause )
m_MediaPlayer.start();
}
}

if( m_MediaPlayer != null ) {
m_MediaPlayer.release();
m_MediaPlayer = null;
}
// ---------------------------------------------

// ---------------------------------------------
// :: Play Video (VideoView)
// ---------------------------------------------
// VideoView

int m_CurrentPosition = 0;

... onSaveInstanceState( ... ) {
m_VideoView.pause();
m_CurrentPosition = m_VideoView.getCurrentPosition();
}

... onRestoreInstanceState( ... ) {
m_VideoView.seekTo( m_CurrentPosition );
m_VideoView.start();
}

if( m_MediaController == null )
m_MediaController = new MediaController( this );
Uri uriVideo = Uri.parse( "android.resource://com.test.TESTAPP/" + R.raw.xxx );
m_VideoView.setVideoURI( uriVideo );
m_MediaController.setSaveEnabled( true );
m_VideoView.setMediaController( m_MediaController );
m_MediaController.show();
m_VideoView.requestFocus();
m_VIdeoView.start();
// ---------------------------------------------

// ---------------------------------------------
// :: Media Scan (SD card)
// ---------------------------------------------
sendBroadcast( new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())) );
// ---------------------------------------------


// ---------------------------------------------
// :: Restrict characters length (Korean/Japanese)
// - EditText 에서 한글/일본어 입력 시 byte 수 제한하기.
// - 간단한 테스트는 Emulator 에서 일본어 입력기로 해보면 된다.
// ---------------------------------------------
        m_Editbox = (EditText)findViewById( R.id.EditText01 );
        m_Editbox.addTextChangedListener( new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub

//Log.d("LOG", s.toString() );
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
//Log.d("LOG", s.toString() );
}

    // byte to int, bytes to hex 는 internet 에서 그냥 찾아서 썼다.
// NOT my code (^^) --------------------
public int getInt(byte[] bytes) {
      int newValue = 0;
      newValue |= (((int) bytes[0]) << 24) & 0xFF000000;
      newValue |= (((int) bytes[1]) << 16) & 0xFF0000;
      newValue |= (((int) bytes[2]) << 8) & 0xFF00;
      newValue |= (((int) bytes[3])) & 0xFF;
      return newValue;
}
public String byteArrayToHex(byte[] ba) {
   if (ba == null || ba.length == 0) {
       return null;
   }

   StringBuffer sb = new StringBuffer(ba.length * 2);
   String hexNumber;
   for (int x = 0; x < ba.length; x++) {
       hexNumber = "0" + Integer.toHexString(0xff & ba[x]);

       sb.append(hexNumber.substring(hexNumber.length() - 2));
   }
   return sb.toString();
}
// NOT my code (^^) --------------------


public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub

//Toast.makeText( myTest1App.this, m_Editbox.getText().toString(), Toast.LENGTH_LONG ).show();
     // 여기에선 8 bytes 를 제한한다.
     // 주의할 점은 한글/일본어는 2 bytes 가 아닌 3 bytes 로 된다.
     // 아래 log 에서 해당 문자와 hex code 를 보면 안다.
int max_len = 8; // restrict 8 bytes. (Korean/Japanese may has 3 bytes. NOT 2 bytes.)
if( s.length() > 0 ) {
int c = s.charAt( s.length()-1 );
String str = Character.toString( s.charAt(s.length()-1) );
int cLen = str.length();

Log.d( "LOG", "s len = " + s.toString().getBytes().length + ", s str = " + s.toString() + ", s hex = " + byteArrayToHex(s.toString().getBytes()) );
//Log.d( "LOG", "byte = " + s.toString().getBytes() + ", len = " + cLen + ", ascii = " + s.charAt(0) + ", dec = " + c + ", hex = " + byteArrayToHex(s.toString().getBytes()) );
Log.d( "LOG", "s.charAt(" + (s.length()-1) + ") -> " + ", = " + str + ", len = " + cLen + ", dec = " + c + ", bytes = " + str.getBytes() + ", len = " + str.getBytes().length + ", hex = " + byteArrayToHex(str.getBytes()) );

if( s.toString().getBytes().length > max_len ) {
s.delete( s.length()-1, s.length() );
m_TextView.setText( s.toString() );
}
}
}
}
);
// ---------------------------------------------


// ---------------------------------------------
// :: Indicator: Notification
// ---------------------------------------------
// http://developer.android.com/guide/topics/ui/notifiers/notifications.html
// http://osdir.com/ml/Android-Beginners/2010-07/msg00649.html

// Path: /android-sdk-windows/platforms/android-4/samples/ApiDemos/src/com/example/android/apis/app
// Source: RemoteService.java
// - AndroidManifest.xml
// - < activity ... android:launchMode="singleTask">
//
// Show a notification while this service is running.
private NotificationManager mNM = null;
private void showNotification() {
        // Indicator: Notification
if( mNM != null ) {
    mNM.cancel( R.string.app_name );

        // Use custom view
//mNM.cancel( R.layout.local_service_controller );

  }

mNM = (NotificationManager)getSystemService( NOTIFICATION_SERVICE );

        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText( R.string.remote_service_started );

        // Set the icon, scrolling text and timestamp
        //Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis() );
        Notification notification = new Notification( -1, text, System.currentTimeMillis() );

        Intent intent = new Intent( this,  testMyActivity.class );
        intent.setFlags( Intent.FLAG_ACTIVITY_SINGLE_TOP );

        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity( this, 0, intent, 0 );

        // Set the info for the views that show in the notification panel.
        //notification.setLatestEventInfo(this, getText(R.string.local_service_label),
        notification.setLatestEventInfo( this, getText(R.string.Info), text, contentIntent );

/*

        // Set custom view
        * DELETE notification.setLatestEventInfo(...)
        RemoteViews contentView = new RemoteViews( getPackageName(), R.layout.local_service_controller );
        ////contentView.setImageViewResource( R.id.image, R.drawable.notification_image );
        ////contentView.setTextViewText( R.id.text, "Hello, this message is in a custom expanded view" );
        //contentView.setImageViewResource( R.id.image, R.drawable.imagefile );
        contentView.setTextViewText( R.id.TextViewIndicator, getText(R.string.app_name) );
        notification.contentView = contentView;

        // Use custom view
        notification.contentIntent = contentIntent;

*/
        // Send the notification.
        // We use a string id because it is a unique number.  We use it later to cancel.
        mNM.notify( R.string.remote_service_started, notification );

        // Use custom view
        //mNM.notify( R.layout.local_service_controller, notification );

}

// Use custom view (ImageView, TextView)
// local_service_controller.xml
// Change (, ) to <, >
(LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:padding="4dip"
android:id="@+id/LinearLayoutIndicator"
    android:layout_width="fill_parent" android:layout_height="fill_parent")
(RelativeLayout android:orientation="horizontal"
android:layout_width="fill_parent" android:layout_height="fill_parent")
    (TextView
     android:id="@+id/TextViewIndicator"
        android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:layout_weight="0"
        android:paddingBottom="4dip"
        android:text="local_service_controller")
    (/TextView)
    (Button android:id="@+id/start"
     android:layout_below="@+id/TextViewIndicator"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="start_service")
    (/Button)
    (Button android:id="@+id/stop"
     android:layout_below="@+id/TextViewIndicator"
     android:layout_toRightOf="@+id/start"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="stop_service")
    (/Button)
(/RelativeLayout)
(/LinearLayout)
// ---------------------------------------------



-----
Cheers,
June