WIP steam downloader
This commit is contained in:
parent
cbd1905d69
commit
b0f7475962
3 changed files with 972 additions and 1 deletions
|
@ -30,6 +30,16 @@
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name="in.celest.xash3d.SteamActivity"
|
||||||
|
android:label="Steam"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="in.celest.xash3d.SteamActivity"/>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="in.celest.xash3d.ShortcutActivity" android:label="@string/text_shortcut" android:theme="@android:style/Theme.Dialog">
|
<activity android:name="in.celest.xash3d.ShortcutActivity" android:label="@string/text_shortcut" android:theme="@android:style/Theme.Dialog">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -73,7 +83,7 @@
|
||||||
<data android:scheme="package" />
|
<data android:scheme="package" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<service android:name="in.celest.xash3d.SteamService" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<!-- Some devices with Android 2.2 should support native activity, it was in unstable hidden API -->
|
<!-- Some devices with Android 2.2 should support native activity, it was in unstable hidden API -->
|
||||||
|
|
225
src/in/celest/xash3d/SteamActivity.java
Normal file
225
src/in/celest/xash3d/SteamActivity.java
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
package in.celest.xash3d;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.util.Log;
|
||||||
|
//import android.content.Context;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import android.text.method.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class SteamActivity extends Activity {
|
||||||
|
LinearLayout output;
|
||||||
|
ScrollView scroll;
|
||||||
|
boolean isScrolling;
|
||||||
|
static SteamActivity mSingleton = null;
|
||||||
|
static final int[] waitSwitch = new int[1];
|
||||||
|
ProgressBar progress;
|
||||||
|
TextView progressLine;
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mSingleton = this;
|
||||||
|
// Build layout
|
||||||
|
LinearLayout launcher = new LinearLayout(this);
|
||||||
|
launcher.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
launcher.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
|
||||||
|
output = new LinearLayout(this);
|
||||||
|
output.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
|
||||||
|
output.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
scroll = new ScrollView(this);
|
||||||
|
|
||||||
|
// Set launch button title here
|
||||||
|
Button startButton = new Button(this);
|
||||||
|
startButton.setText("Start");
|
||||||
|
LayoutParams buttonParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
buttonParams.gravity = 5;
|
||||||
|
startButton.setLayoutParams(buttonParams);
|
||||||
|
startButton.setOnClickListener( new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if( SteamService.mSingleton == null )
|
||||||
|
startService(new Intent(SteamActivity.this, SteamService.class));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Button stopButton = new Button(this);
|
||||||
|
stopButton.setText("Stop");
|
||||||
|
LayoutParams stopbuttonParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
stopbuttonParams.gravity = 5;
|
||||||
|
stopButton.setLayoutParams(stopbuttonParams);
|
||||||
|
stopButton.setOnClickListener( new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if( SteamService.mSingleton != null )
|
||||||
|
SteamService.mSingleton.cancelThread();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
LinearLayout buttons = new LinearLayout(this);
|
||||||
|
buttons.addView(startButton);
|
||||||
|
buttons.addView(stopButton);
|
||||||
|
launcher.addView(buttons);
|
||||||
|
scroll.addView(output);
|
||||||
|
progress = new ProgressBar(this, null,
|
||||||
|
android.R.attr.progressBarStyleHorizontal);
|
||||||
|
progress.setMax(100);
|
||||||
|
progress.setVisibility(View.GONE);
|
||||||
|
progressLine = new TextView(this);
|
||||||
|
progressLine.setVisibility(View.GONE);
|
||||||
|
progressLine.setTextAppearance(this, android.R.attr.textAppearanceLarge);
|
||||||
|
launcher.addView( progressLine );
|
||||||
|
launcher.addView(progress);
|
||||||
|
launcher.addView(scroll);
|
||||||
|
setContentView(launcher);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized( waitSwitch )
|
||||||
|
{
|
||||||
|
waitSwitch.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e ) {}
|
||||||
|
}
|
||||||
|
public void progressUpdate( String str, int p)
|
||||||
|
{
|
||||||
|
runOnUiThread(new ProgressCallback( str, p ));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void printText(String str)
|
||||||
|
{
|
||||||
|
runOnUiThread(new OutputCallback( str ));
|
||||||
|
}
|
||||||
|
|
||||||
|
String promptDialog(final String title, final String prompt, final boolean passwd)
|
||||||
|
{
|
||||||
|
final String[] result = new String[1];
|
||||||
|
runOnUiThread(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
final EditText edit = new EditText(mSingleton);
|
||||||
|
if( passwd )
|
||||||
|
edit.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
new AlertDialog.Builder(mSingleton)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(prompt)
|
||||||
|
.setView(edit)
|
||||||
|
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int whichButton) {
|
||||||
|
synchronized(result)
|
||||||
|
{
|
||||||
|
result[0] = edit.getText().toString();
|
||||||
|
result.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int whichButton) {
|
||||||
|
synchronized(result)
|
||||||
|
{
|
||||||
|
result[0] = null;
|
||||||
|
result.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
synchronized(result)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
result.wait();
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
runOnUiThread(new OutputCallback(e.getMessage()));
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callbacks to interact with UI from other threads
|
||||||
|
class OutputCallback implements Runnable {
|
||||||
|
String str;
|
||||||
|
OutputCallback(String s) { str = s; }
|
||||||
|
public void run() {
|
||||||
|
TextView line = new TextView(SteamActivity.this);
|
||||||
|
line.setText(str);
|
||||||
|
|
||||||
|
line.setTextAppearance(SteamActivity.this, android.R.attr.textAppearanceSmall);
|
||||||
|
line.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
|
||||||
|
progress.setVisibility(View.GONE);
|
||||||
|
progressLine.setVisibility(View.GONE);
|
||||||
|
if(output.getChildCount() > 256)
|
||||||
|
output.removeViewAt(0);
|
||||||
|
output.addView(line);
|
||||||
|
if( !isScrolling )
|
||||||
|
scroll.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
scroll.fullScroll(ScrollView.FOCUS_DOWN);
|
||||||
|
isScrolling = false;
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
isScrolling = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ProgressCallback implements Runnable {
|
||||||
|
String str;
|
||||||
|
int p;
|
||||||
|
ProgressCallback(String s, int pr) { str = s; p = pr; }
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
progressLine.setText( str );
|
||||||
|
progressLine.setVisibility(View.VISIBLE);
|
||||||
|
progress.setProgress(p);
|
||||||
|
progress.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void onDestroy()
|
||||||
|
{
|
||||||
|
mSingleton = null;
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
736
src/in/celest/xash3d/SteamService.java
Normal file
736
src/in/celest/xash3d/SteamService.java
Normal file
|
@ -0,0 +1,736 @@
|
||||||
|
package in.celest.xash3d;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.*;
|
||||||
|
import android.app.*;
|
||||||
|
import android.view.View;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import in.celest.xash3d.hl.R;
|
||||||
|
import android.widget.RemoteViews.*;
|
||||||
|
import android.util.*;
|
||||||
|
import java.util.concurrent.atomic.*;
|
||||||
|
|
||||||
|
|
||||||
|
enum ProcessState{ UNPACK, LAUNCH, COMMAND, WAIT, DOWNLOAD };
|
||||||
|
|
||||||
|
public class SteamService extends Service
|
||||||
|
{
|
||||||
|
final String TAG = "SteamService";
|
||||||
|
public static SteamService mSingleton;
|
||||||
|
Notification notification;
|
||||||
|
NotificationManager notificationManager = null;
|
||||||
|
String localPath;
|
||||||
|
String filesDir;
|
||||||
|
class RestartException extends Exception {};
|
||||||
|
class CancelException extends Exception {};
|
||||||
|
|
||||||
|
|
||||||
|
static BackgroundThread mBgThread;
|
||||||
|
SharedPreferences mPref;
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mSingleton = this;
|
||||||
|
mPref = getSharedPreferences("steam", 0);
|
||||||
|
synchronized(mPref)
|
||||||
|
{
|
||||||
|
// test
|
||||||
|
mPref.edit()
|
||||||
|
.putStringSet("pending_verify", new HashSet<String>(Arrays.asList("70")))
|
||||||
|
.putStringSet("pending_download", new HashSet<String>(Arrays.asList("70")))
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
Log.d(TAG, "onCreate");
|
||||||
|
}
|
||||||
|
|
||||||
|
void notificationInit()
|
||||||
|
{
|
||||||
|
// init notification and foreground service
|
||||||
|
Intent intent = new Intent(this, SteamActivity.class);
|
||||||
|
final PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||||
|
getApplicationContext(), 0, intent, 0);
|
||||||
|
notification = new Notification(R.drawable.ic_launcher,
|
||||||
|
"SteamCMD download", System.currentTimeMillis());
|
||||||
|
notification.flags = notification.flags
|
||||||
|
| Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE;
|
||||||
|
notification.contentView = new RemoteViews(getApplicationContext()
|
||||||
|
.getPackageName(), R.layout.notify);
|
||||||
|
|
||||||
|
notification.contentView.setViewVisibility( R.id.status_progress, View.GONE );
|
||||||
|
notification.contentIntent = pendingIntent;
|
||||||
|
if( notificationManager == null )
|
||||||
|
notificationManager = (NotificationManager) getApplicationContext()
|
||||||
|
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
startForeground(100, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dont create too much notification intents, it may crash system
|
||||||
|
long lastNotify = 0;
|
||||||
|
void progressUpdate( String text, int progress) {
|
||||||
|
if( SteamActivity.mSingleton != null )
|
||||||
|
SteamActivity.mSingleton.progressUpdate( text, progress );
|
||||||
|
if( notification == null )
|
||||||
|
notificationInit();
|
||||||
|
long notify = System.currentTimeMillis();
|
||||||
|
// allow 1s interval
|
||||||
|
if( notify - lastNotify < 999 )
|
||||||
|
return;
|
||||||
|
lastNotify = notify;
|
||||||
|
notification.contentView.setTextViewText(R.id.status_text, text);
|
||||||
|
notification.contentView.setProgressBar(R.id.status_progress, 100, progress, false);
|
||||||
|
notification.contentView.setViewVisibility( R.id.status_progress, View.VISIBLE );
|
||||||
|
|
||||||
|
notificationManager.notify(100, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
Log.d(TAG, "onStartCommand");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( mBgThread != null )
|
||||||
|
{
|
||||||
|
// prevent running multiple instances
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mBgThread.process.destroy();
|
||||||
|
synchronized(mBgThread)
|
||||||
|
{
|
||||||
|
mBgThread.wait(5000);
|
||||||
|
}
|
||||||
|
mBgThread = null;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filesDir = getFilesDir().toString();
|
||||||
|
localPath = mPref.getString( "steam_path", "/sdcard/steam/" );
|
||||||
|
if( !localPath.endsWith("/") ) localPath += '/';
|
||||||
|
notificationInit();
|
||||||
|
mBgThread = new BackgroundThread();
|
||||||
|
mBgThread.start();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
printText(e.toString());
|
||||||
|
}
|
||||||
|
return super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDestroy() {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( mBgThread != null )
|
||||||
|
mBgThread.interrupt();
|
||||||
|
mBgThread.process.destroy();
|
||||||
|
}catch( Exception e ){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
mSingleton = null;
|
||||||
|
super.onDestroy();
|
||||||
|
Log.d(TAG, "onDestroy");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
Log.d(TAG, "onBind");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
void printText(String text)
|
||||||
|
{
|
||||||
|
// register notification first
|
||||||
|
if( notification == null )
|
||||||
|
notificationInit();
|
||||||
|
long notify = System.currentTimeMillis();
|
||||||
|
if( notify - lastNotify >= 100 )
|
||||||
|
{
|
||||||
|
lastNotify = notify;
|
||||||
|
notification.contentView.setTextViewText(R.id.status_text,
|
||||||
|
text);
|
||||||
|
|
||||||
|
notificationManager.notify(100, notification);
|
||||||
|
}
|
||||||
|
// if activiy exist, print to it's screen
|
||||||
|
if( SteamActivity.mSingleton == null )
|
||||||
|
return;
|
||||||
|
try{
|
||||||
|
SteamActivity.mSingleton.printText( text );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// block current thread and show dialog
|
||||||
|
String promptDialog(String title, String message, boolean passwd)
|
||||||
|
{
|
||||||
|
Intent intent = new Intent(this, SteamActivity.class);
|
||||||
|
final PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||||
|
getApplicationContext(), 0, intent, 0);
|
||||||
|
// request user interaction
|
||||||
|
Notification n = new Notification(R.drawable.ic_launcher, message, System.currentTimeMillis());
|
||||||
|
n.flags |= Notification.FLAG_HIGH_PRIORITY;
|
||||||
|
//n.priority = Notification.PRIORITY_MAX;
|
||||||
|
n.contentIntent = pendingIntent;
|
||||||
|
n.contentView = new RemoteViews(getPackageName(), R.layout.notify);
|
||||||
|
n.contentView.setViewVisibility(R.id.status_progress, View.GONE);
|
||||||
|
n.contentView.setTextViewText( R.id.status_text, message );
|
||||||
|
if( notificationManager == null )
|
||||||
|
notificationManager = (NotificationManager) getApplicationContext()
|
||||||
|
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
notificationManager.notify(101, n);
|
||||||
|
notification.contentView.setTextViewText(R.id.status_text, message);
|
||||||
|
|
||||||
|
notificationManager.notify(100, notification);
|
||||||
|
waitForActivity();
|
||||||
|
notificationManager.cancel(101);
|
||||||
|
if( SteamActivity.mSingleton == null )
|
||||||
|
return null;
|
||||||
|
try{
|
||||||
|
return SteamActivity.mSingleton.promptDialog(title, message, passwd);
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return next AppID, requested to verify
|
||||||
|
private synchronized String getVerify()
|
||||||
|
{
|
||||||
|
synchronized(mPref)
|
||||||
|
{
|
||||||
|
Set<String> prefs = mPref.getStringSet("pending_verify",null);
|
||||||
|
if( prefs == null || prefs.isEmpty() )
|
||||||
|
return null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (String)prefs.toArray()[0];
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move from pending verification to verified
|
||||||
|
private synchronized void setVerified(String id)
|
||||||
|
{
|
||||||
|
synchronized(mPref)
|
||||||
|
{
|
||||||
|
Set<String> prefs = mPref.getStringSet("pending_verify",(Set<String>)new HashSet<String>());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( !prefs.isEmpty() && prefs.contains(id) )
|
||||||
|
prefs.remove(id);
|
||||||
|
}
|
||||||
|
catch(Exception e){
|
||||||
|
prefs = new HashSet<String>();
|
||||||
|
}
|
||||||
|
Set<String> verified = mPref.getStringSet("verified",new HashSet<String>());
|
||||||
|
verified.add( id );
|
||||||
|
mPref.edit()
|
||||||
|
.putStringSet("verified",verified)
|
||||||
|
.putStringSet("pending_verify", prefs)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// just remove from pending set
|
||||||
|
private synchronized void verifyFail(String id)
|
||||||
|
{
|
||||||
|
synchronized(mPref)
|
||||||
|
{
|
||||||
|
Set<String> prefs = mPref.getStringSet("pending_verify",(Set<String>)new HashSet<String>());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( !prefs.isEmpty() && prefs.contains(id) )
|
||||||
|
prefs.remove(id);
|
||||||
|
}
|
||||||
|
catch(Exception e){
|
||||||
|
prefs = new HashSet<String>();
|
||||||
|
}
|
||||||
|
mPref.edit()
|
||||||
|
.putStringSet("pending_verify", prefs)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return next AppID, scheduled to download
|
||||||
|
private synchronized String getDownload()
|
||||||
|
{
|
||||||
|
synchronized(mPref)
|
||||||
|
{
|
||||||
|
Set<String> prefs = mPref.getStringSet("pending_download",null);
|
||||||
|
if( prefs == null || prefs.isEmpty() )
|
||||||
|
return null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (String)prefs.toArray()[0];
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove from download set
|
||||||
|
private synchronized void clearDownload(String id)
|
||||||
|
{
|
||||||
|
synchronized(mPref)
|
||||||
|
{
|
||||||
|
Set<String> prefs = mPref.getStringSet("pending_download",(Set<String>)new HashSet<String>());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( !prefs.isEmpty() && prefs.contains(id) )
|
||||||
|
prefs.remove(id);
|
||||||
|
}
|
||||||
|
catch(Exception e){
|
||||||
|
prefs = new HashSet<String>();
|
||||||
|
}
|
||||||
|
mPref.edit()
|
||||||
|
.putStringSet("pending_download", prefs)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForActivity()
|
||||||
|
{
|
||||||
|
if( SteamActivity.mSingleton != null )
|
||||||
|
// Nothing to wait
|
||||||
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized( SteamActivity.waitSwitch )
|
||||||
|
{
|
||||||
|
SteamActivity.waitSwitch.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
Log.d( TAG, "waitForActivity failed: " + e.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// separate thread to control remote process
|
||||||
|
class BackgroundThread extends Thread {
|
||||||
|
OutputStream processInput = null;
|
||||||
|
|
||||||
|
ProcessState state;;
|
||||||
|
boolean need_reset_config = false;
|
||||||
|
boolean skipQemu = false;
|
||||||
|
String lastID;
|
||||||
|
Process process;
|
||||||
|
AtomicBoolean needDestroy;
|
||||||
|
|
||||||
|
void downloadFile( String strurl, String path ) throws IOException, CancelException
|
||||||
|
{
|
||||||
|
URL url = new URL(strurl);
|
||||||
|
int count;
|
||||||
|
printText("Downloading " + path);
|
||||||
|
notification.contentView.setViewVisibility( R.id.status_progress, View.GONE );
|
||||||
|
URLConnection conection = url.openConnection();
|
||||||
|
conection.connect();
|
||||||
|
|
||||||
|
// this will be useful so that you can show a tipical 0-100%
|
||||||
|
// progress bar
|
||||||
|
int lenghtOfFile = conection.getContentLength();
|
||||||
|
|
||||||
|
// download the file
|
||||||
|
InputStream input = new BufferedInputStream(url.openStream(), 8192);
|
||||||
|
|
||||||
|
// Output stream
|
||||||
|
OutputStream output = new FileOutputStream(path);
|
||||||
|
|
||||||
|
byte data[] = new byte[1024];
|
||||||
|
|
||||||
|
long total = 0;
|
||||||
|
int lastprogress = 0;
|
||||||
|
|
||||||
|
while ( !needDestroy.get() && (count = input.read(data)) != -1) {
|
||||||
|
total += count;
|
||||||
|
// publishing the progress....
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( (lenghtOfFile > 0) && ((int) ((total * 100) / lenghtOfFile) - lastprogress > 1) )
|
||||||
|
progressUpdate("Downloading " + path, lastprogress = (int) ((total * 100) / lenghtOfFile));
|
||||||
|
}
|
||||||
|
catch( Exception e ) {}
|
||||||
|
|
||||||
|
// writing data to file
|
||||||
|
output.write(data, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushing output
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
// closing streams
|
||||||
|
output.close();
|
||||||
|
input.close();
|
||||||
|
if(needDestroy.get())
|
||||||
|
throw new CancelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// called on every line, encef with \n
|
||||||
|
void processLine( String str ) throws RestartException,IOException
|
||||||
|
{
|
||||||
|
// downloading game
|
||||||
|
if( str.startsWith( " Update state (") )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String statestr = str.substring(20).trim();
|
||||||
|
//printText(statestr);
|
||||||
|
|
||||||
|
String p = statestr.substring(statestr.indexOf('(')+1, statestr.indexOf(')'));
|
||||||
|
progressUpdate( statestr, (int)(100 * Float.valueOf(p.substring(0,p.indexOf('/')).trim())/Float.valueOf(p.substring(p.indexOf('/')+1).trim())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
//e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// downloading steam update
|
||||||
|
else if( !str.isEmpty() && (str.charAt(0) == '[') && str.contains( "] Downloading update ("))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
progressUpdate(str, (int) (1*Float.valueOf( str.substring(1, str.indexOf('%')).trim())) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// switch to command mode
|
||||||
|
else if( str.contains( "Logged in OK" ) )
|
||||||
|
{
|
||||||
|
state = ProcessState.WAIT;
|
||||||
|
printText("Successfully logged in, now starting to send commands");
|
||||||
|
|
||||||
|
}
|
||||||
|
// avoid steamcmd bug with permanent login failure
|
||||||
|
else if( str.contains( "FAILED with result code " ) )
|
||||||
|
{
|
||||||
|
// login failure, remove config and try again;
|
||||||
|
need_reset_config = true;
|
||||||
|
processInput.write("quit\n".getBytes());
|
||||||
|
processInput.flush();
|
||||||
|
// hack: process does not restart itself, try force restart it;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sleep(2000);
|
||||||
|
}
|
||||||
|
catch(Exception e){}
|
||||||
|
throw new RestartException();
|
||||||
|
}
|
||||||
|
// download completed
|
||||||
|
else if( str.contains("Success! App '") && ( str.contains("' fully installed." ) || str.contains("' already up to date." ) ) )
|
||||||
|
{
|
||||||
|
String id = str.substring(str.indexOf("'")+1);
|
||||||
|
id = id.substring(0, id.indexOf("'"));
|
||||||
|
printText("AppID " + id + " downloaded successfully!");
|
||||||
|
clearDownload( id );
|
||||||
|
}
|
||||||
|
// process hangs up
|
||||||
|
else if( str.contains("Fatal assert failed"))
|
||||||
|
throw new RestartException();
|
||||||
|
// ..AppID %d:\n
|
||||||
|
// - release state: ...
|
||||||
|
else if( str.contains("AppID ") )
|
||||||
|
{
|
||||||
|
lastID = str.substring( str.indexOf("AppID ")+ 6, str.indexOf(':'));
|
||||||
|
}
|
||||||
|
// license status
|
||||||
|
else if( str.startsWith(" - release state: ") )
|
||||||
|
{
|
||||||
|
if( str.contains("Subscribed" ) )
|
||||||
|
{
|
||||||
|
setVerified( lastID );
|
||||||
|
printText("AppID " + lastID + " confirmed");
|
||||||
|
|
||||||
|
}
|
||||||
|
else if( str.contains("No License" ) )
|
||||||
|
{
|
||||||
|
verifyFail( lastID );
|
||||||
|
printText("AppID " + lastID + " HAS NO LICENSE");
|
||||||
|
// do not try to download it
|
||||||
|
clearDownload( lastID );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notification.contentView.setViewVisibility( R.id.status_progress, View.GONE );
|
||||||
|
printText(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// called on every char in line until return true
|
||||||
|
boolean processPartial( String str ) throws CancelException, IOException
|
||||||
|
{
|
||||||
|
{
|
||||||
|
if( str.contains( "Steam>" ) )
|
||||||
|
{
|
||||||
|
// not logged in yet
|
||||||
|
if( state == ProcessState.LAUNCH )
|
||||||
|
{
|
||||||
|
String login = promptDialog("Login", "Please enter your login", false);
|
||||||
|
if( login == null )
|
||||||
|
{
|
||||||
|
processInput.write( ( "exit\n").getBytes() );
|
||||||
|
throw new CancelException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
processInput.write( ( "login " + login + "\n").getBytes() );
|
||||||
|
}
|
||||||
|
// already logged in, send commands
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = ProcessState.COMMAND;
|
||||||
|
String cmd = null;
|
||||||
|
if( getVerify() != null )
|
||||||
|
cmd = "app_status " + getVerify();
|
||||||
|
else if( getDownload() != null )
|
||||||
|
cmd = "app_update " + getDownload() + " verify";
|
||||||
|
else
|
||||||
|
cmd = "exit";
|
||||||
|
if( cmd != null )
|
||||||
|
{
|
||||||
|
processInput.write( ( cmd + '\n').getBytes());
|
||||||
|
processInput.flush();
|
||||||
|
printText("cmd: " + cmd);
|
||||||
|
state = ProcessState.WAIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// user interaction
|
||||||
|
if( str.startsWith("password:" ))
|
||||||
|
{
|
||||||
|
String passwd = promptDialog("Password", "Please enter your password", true);
|
||||||
|
if( passwd == null )
|
||||||
|
{
|
||||||
|
processInput.write( ( "\n").getBytes() );
|
||||||
|
throw new CancelException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
processInput.write( (passwd + '\n').getBytes() );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if( str.startsWith("Steam Guard code:" ))
|
||||||
|
{
|
||||||
|
String passwd = promptDialog("Steam Guard code", "Please enter your SteamGuard code", true);
|
||||||
|
if( passwd == null )
|
||||||
|
{
|
||||||
|
processInput.write( ( "\n").getBytes() );
|
||||||
|
throw new CancelException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
processInput.write( (passwd + '\n').getBytes() );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// launch procesc and process all outout
|
||||||
|
int launchProcess( String command ) throws Exception
|
||||||
|
{
|
||||||
|
int result = 255;
|
||||||
|
printText("process start: " + command);
|
||||||
|
process = Runtime.getRuntime().exec( command );
|
||||||
|
InputStream reader = process.getInputStream();
|
||||||
|
processInput = process.getOutputStream();
|
||||||
|
BufferedReader readererr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||||
|
int ch;
|
||||||
|
boolean processed = false;
|
||||||
|
|
||||||
|
String str = "";
|
||||||
|
while ( !needDestroy.get() && ((ch = reader.read()) >= 0) ) {
|
||||||
|
if( ch == '\n' )
|
||||||
|
{
|
||||||
|
processLine( str );
|
||||||
|
str = "";
|
||||||
|
processed = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
str += (char)ch;
|
||||||
|
// performance: skip comparsions on Update state lines
|
||||||
|
if( str == " " )
|
||||||
|
processed = true;
|
||||||
|
if( !processed )
|
||||||
|
processed = processPartial( str );
|
||||||
|
}
|
||||||
|
if(needDestroy.get())
|
||||||
|
throw new CancelException();
|
||||||
|
processLine( str );
|
||||||
|
printText("process closed stdout");
|
||||||
|
|
||||||
|
// flush err buffer
|
||||||
|
while( (str = readererr.readLine()) != null && !str.isEmpty() )
|
||||||
|
printText(str);
|
||||||
|
reader.close();
|
||||||
|
processInput.close();
|
||||||
|
|
||||||
|
// Waits for the command to finish.
|
||||||
|
if( process != null )
|
||||||
|
result = process.waitFor();
|
||||||
|
printText("process end: " + result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
final private String repoUrl = "https://raw.githubusercontent.com/mittorn/steamcmd-deps/master/";
|
||||||
|
// download from github repo
|
||||||
|
private void downloadDep( String path ) throws IOException,CancelException
|
||||||
|
{
|
||||||
|
downloadFile( repoUrl + path, localPath + path );
|
||||||
|
}
|
||||||
|
|
||||||
|
// make directories for local path
|
||||||
|
private void mkdirs( String path )
|
||||||
|
{
|
||||||
|
new File( localPath + path ).mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// download all files if not yet downloaded
|
||||||
|
void downloadAll() throws IOException,CancelException
|
||||||
|
{
|
||||||
|
if( !skipQemu && !new File( filesDir + "/qemu.downloaded").exists() )
|
||||||
|
{
|
||||||
|
File qemu = new File( filesDir + "/qemu");
|
||||||
|
if( qemu.exists() ) qemu.delete();
|
||||||
|
downloadFile( repoUrl + "qemu-armeabi-v7a", filesDir + "/qemu" );
|
||||||
|
new File( filesDir + "/qemu.downloaded").createNewFile();
|
||||||
|
}
|
||||||
|
if( new File( localPath + ".downloaded").exists() )
|
||||||
|
return;
|
||||||
|
mkdirs( "" );
|
||||||
|
downloadDep("gzip");
|
||||||
|
mkdirs( "lib" );
|
||||||
|
downloadDep( "lib/libc.so.6" );
|
||||||
|
downloadDep( "lib/libdl.so.2" );
|
||||||
|
downloadDep( "lib/libgcc_s.so.1" );
|
||||||
|
downloadDep( "lib/libm.so.6" );
|
||||||
|
downloadDep( "lib/libnss_dns.so.2" );
|
||||||
|
downloadDep( "lib/libpthread.so.0" );
|
||||||
|
downloadDep( "lib/libresolv.so.2" );
|
||||||
|
downloadDep( "lib/librt.so.1" );
|
||||||
|
mkdirs( "linux32" );
|
||||||
|
downloadDep( "linux32/ld-linux.so.2" );
|
||||||
|
downloadDep( "resolvconf-override.so" );
|
||||||
|
mkdirs( "sources" );
|
||||||
|
downloadDep( "sources/debian.txt" );
|
||||||
|
downloadDep( "sources/qemu.patch" );
|
||||||
|
downloadDep( "sources/qemu.txt" );
|
||||||
|
downloadDep( "sources/resolvconf-override.c" );
|
||||||
|
downloadDep( "tar" );
|
||||||
|
downloadDep( "killall" );
|
||||||
|
if( skipQemu )
|
||||||
|
downloadDep( "start-x86.sh" );
|
||||||
|
else
|
||||||
|
downloadDep( "start-qemu.sh" );
|
||||||
|
downloadFile( "http://media.steampowered.com/client/installer/steamcmd_linux.tar.gz", localPath + "steamcmd_linux.tar.gz" );
|
||||||
|
|
||||||
|
new File( localPath + ".downloaded").createNewFile();
|
||||||
|
}
|
||||||
|
int launchX86(String command) throws Exception
|
||||||
|
{
|
||||||
|
if( skipQemu )
|
||||||
|
return launchProcess( "sh " + localPath + "start-x86.sh " + localPath + ' ' + command );
|
||||||
|
else
|
||||||
|
return launchProcess( "sh " + localPath + "start-qemu.sh " + localPath + " "+ filesDir + "/qemu " + command );
|
||||||
|
}
|
||||||
|
void unpackAll() throws Exception
|
||||||
|
{
|
||||||
|
if( new File( localPath + ".unpacked").exists() )
|
||||||
|
return;
|
||||||
|
launchProcess( "chmod 777 " + filesDir + "/qemu" );
|
||||||
|
launchX86( localPath + "gzip -d steamcmd_linux.tar.gz" );
|
||||||
|
launchX86( localPath + "tar xvf steamcmd_linux.tar" );
|
||||||
|
new File( localPath + "steamcmd_linux.tar" ).delete();
|
||||||
|
new File( localPath + ".unpacked").createNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
super.run();
|
||||||
|
needDestroy = new AtomicBoolean(false);
|
||||||
|
try {
|
||||||
|
if( skipQemu = (System.getProperty("ro.product.cpu.abi") == "x86") )
|
||||||
|
localPath = filesDir + '/';
|
||||||
|
state = ProcessState.UNPACK;
|
||||||
|
downloadAll();
|
||||||
|
unpackAll();
|
||||||
|
int result;
|
||||||
|
do{
|
||||||
|
state = ProcessState.LAUNCH;
|
||||||
|
killAll();
|
||||||
|
if( need_reset_config )
|
||||||
|
try{
|
||||||
|
new File( localPath + "Steam/config/config.vdf" ).delete();
|
||||||
|
}
|
||||||
|
catch( Exception e) {}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = launchX86( localPath + "linux32/steamcmd" );
|
||||||
|
}
|
||||||
|
catch( RestartException e )
|
||||||
|
{
|
||||||
|
// 42 is restart magick in steam
|
||||||
|
result = 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while( result == 42 || getVerify() != null || getDownload() != null ) ;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
printText("Fatal: " + e.toString() + ": "+ e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
needDestroy.getAndSet(false);
|
||||||
|
killAll();
|
||||||
|
}
|
||||||
|
finally{}
|
||||||
|
printText("Background thread end!");
|
||||||
|
mBgThread = null;
|
||||||
|
needDestroy.getAndSet(false);
|
||||||
|
stopSelf();
|
||||||
|
//return null;
|
||||||
|
}
|
||||||
|
public void killAll()
|
||||||
|
{
|
||||||
|
|
||||||
|
// kill old processes, but only if they were running more
|
||||||
|
// than 2 seconds to keep killall itself
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( skipQemu )
|
||||||
|
launchX86( localPath + "killall -o5s -9 ld-linux.so.2" );
|
||||||
|
else
|
||||||
|
launchX86( localPath + "killall -o5s -9 qemu" );
|
||||||
|
}
|
||||||
|
catch( Exception e ){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void cancelThread()
|
||||||
|
{
|
||||||
|
if( mBgThread == null )
|
||||||
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mBgThread.needDestroy.getAndSet(true);
|
||||||
|
mBgThread.interrupt();
|
||||||
|
// destroy process to cause exception
|
||||||
|
mBgThread.process.destroy();
|
||||||
|
}catch( Exception e ){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue