/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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.
*/
package jackpal.androidterm;
import android.app.Service;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.*;
import android.content.Intent;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.app.Notification;
import android.app.PendingIntent;
import jackpal.androidterm.emulatorview.TermSession;
import jackpal.androidterm.compat.ServiceForegroundCompat;
import jackpal.androidterm.libtermexec.v1.*;
import jackpal.androidterm.util.SessionList;
import jackpal.androidterm.util.TermSettings;
import java.util.UUID;
public class TermService extends Service implements TermSession.FinishCallback
{
/* Parallels the value of START_STICKY on API Level >= 5 */
private static final int COMPAT_START_STICKY = 1;
private static final int RUNNING_NOTIFICATION = 1;
private ServiceForegroundCompat compat;
private SessionList mTermSessions;
public class TSBinder extends Binder {
TermService getService() {
Log.i("TermService", "Activity binding to service");
return TermService.this;
}
}
private final IBinder mTSBinder = new TSBinder();
@Override
public void onStart(Intent intent, int flags) {
}
/* This should be @Override if building with API Level >=5 */
public int onStartCommand(Intent intent, int flags, int startId) {
return COMPAT_START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
if (TermExec.SERVICE_ACTION_V1.equals(intent.getAction())) {
Log.i("TermService", "Outside process called onBind()");
return new RBinder();
} else {
Log.i("TermService", "Activity called onBind()");
return mTSBinder;
}
}
@Override
public void onCreate() {
// should really belong to the Application class, but we don't use one...
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor editor = prefs.edit();
String defValue = getDir("HOME", MODE_PRIVATE).getAbsolutePath();
String homePath = prefs.getString("home_path", defValue);
editor.putString("home_path", homePath);
editor.commit();
compat = new ServiceForegroundCompat(this);
mTermSessions = new SessionList();
/* Put the service in the foreground. */
Notification notification = new Notification(R.drawable.ic_stat_service_notification_icon, getText(R.string.service_notify_text), System.currentTimeMillis());
notification.flags |= Notification.FLAG_ONGOING_EVENT;
Intent notifyIntent = new Intent(this, Term.class);
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.application_terminal), getText(R.string.service_notify_text), pendingIntent);
compat.startForeground(RUNNING_NOTIFICATION, notification);
Log.d(TermDebug.LOG_TAG, "TermService started");
return;
}
@Override
public void onDestroy() {
compat.stopForeground(true);
for (TermSession session : mTermSessions) {
/* Don't automatically remove from list of sessions -- we clear the
* list below anyway and we could trigger
* ConcurrentModificationException if we do */
session.setFinishCallback(null);
session.finish();
}
mTermSessions.clear();
return;
}
public SessionList getSessions() {
return mTermSessions;
}
public void onSessionFinish(TermSession session) {
mTermSessions.remove(session);
}
private final class RBinder extends ITerminal.Stub {
@Override
public IntentSender startSession(final ParcelFileDescriptor pseudoTerminalMultiplexerFd,
final ResultReceiver callback) {
final String sessionHandle = UUID.randomUUID().toString();
// distinct Intent Uri and PendingIntent requestCode must be sufficient to avoid collisions
final Intent switchIntent = new Intent(RemoteInterface.PRIVACT_OPEN_NEW_WINDOW)
.setData(Uri.parse(sessionHandle))
.addCategory(Intent.CATEGORY_DEFAULT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(RemoteInterface.PRIVEXTRA_TARGET_WINDOW, sessionHandle);
final PendingIntent result = PendingIntent.getActivity(getApplicationContext(), sessionHandle.hashCode(),
switchIntent, 0);
final PackageManager pm = getPackageManager();
final String[] pkgs = pm.getPackagesForUid(getCallingUid());
if (pkgs == null || pkgs.length == 0)
return null;
for (String packageName:pkgs) {
try {
final PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
final ApplicationInfo appInfo = pkgInfo.applicationInfo;
if (appInfo == null)
continue;
final CharSequence label = pm.getApplicationLabel(appInfo);
if (!TextUtils.isEmpty(label)) {
final String niceName = label.toString();
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
GenericTermSession session = null;
try {
final TermSettings settings = new TermSettings(getResources(),
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
session = new BoundSession(pseudoTerminalMultiplexerFd, settings, niceName);
mTermSessions.add(session);
session.setHandle(sessionHandle);
session.setFinishCallback(new RBinderCleanupCallback(result, callback));
session.setTitle("");
session.initializeEmulator(80, 24);
} catch (Exception whatWentWrong) {
Log.e("TermService", "Failed to bootstrap AIDL session: "
+ whatWentWrong.getMessage());
if (session != null)
session.finish();
}
}
});
return result.getIntentSender();
}
} catch (PackageManager.NameNotFoundException ignore) {}
}
return null;
}
}
private final class RBinderCleanupCallback implements TermSession.FinishCallback {
private final PendingIntent result;
private final ResultReceiver callback;
public RBinderCleanupCallback(PendingIntent result, ResultReceiver callback) {
this.result = result;
this.callback = callback;
}
@Override
public void onSessionFinish(TermSession session) {
result.cancel();
callback.send(0, new Bundle());
mTermSessions.remove(session);
}
}
}