diff options
Diffstat (limited to 'services')
17 files changed, 1067 insertions, 121 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7ecf248..1e21e1c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -343,12 +343,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_INET_CONDITION_HOLD_END = 5; /** - * used internally to set enable/disable cellular data - * arg1 = ENBALED or DISABLED - */ - private static final int EVENT_SET_MOBILE_DATA = 7; - - /** * used internally to clear a wakelock when transitioning * from one net to another */ @@ -1822,20 +1816,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return true; } - /** - * @see ConnectivityManager#getMobileDataEnabled() - */ - public boolean getMobileDataEnabled() { - // TODO: This detail should probably be in DataConnectionTracker's - // which is where we store the value and maybe make this - // asynchronous. - enforceAccessPermission(); - boolean retVal = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MOBILE_DATA, 1) == 1; - if (VDBG) log("getMobileDataEnabled returning " + retVal); - return retVal; - } - public void setDataDependency(int networkType, boolean met) { enforceConnectivityInternalPermission(); @@ -1908,22 +1888,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } }; - /** - * @see ConnectivityManager#setMobileDataEnabled(boolean) - */ - public void setMobileDataEnabled(boolean enabled) { - enforceChangePermission(); - if (DBG) log("setMobileDataEnabled(" + enabled + ")"); - - mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, - (enabled ? ENABLED : DISABLED), 0)); - } - - private void handleSetMobileData(boolean enabled) { - // TODO - handle this - probably generalize passing in a transport type and send to the - // factories? - } - @Override public void setPolicyDataEnable(int networkType, boolean enabled) { // only someone like NPMS should only be calling us @@ -3315,11 +3279,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleInetConditionHoldEnd(netType, sequence); break; } - case EVENT_SET_MOBILE_DATA: { - boolean enabled = (msg.arg1 == ENABLED); - handleSetMobileData(enabled); - break; - } case EVENT_APPLY_GLOBAL_HTTP_PROXY: { handleDeprecatedGlobalHttpProxy(); break; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 88bebcb..fc808ec 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7085,6 +7085,10 @@ public final class ActivityManagerService extends ActivityManagerNative * Creates a new RecentTaskInfo from a TaskRecord. */ private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) { + // Update the task description to reflect any changes in the task stack + tr.updateTaskDescription(); + + // Compose the recent task info ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); rti.id = tr.mActivities.isEmpty() ? -1 : tr.taskId; @@ -9574,11 +9578,13 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - mRecentTasks = mTaskPersister.restoreTasksLocked(); - if (!mRecentTasks.isEmpty()) { - mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks); + if (mRecentTasks == null) { + mRecentTasks = mTaskPersister.restoreTasksLocked(); + if (!mRecentTasks.isEmpty()) { + mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks); + } + mTaskPersister.startPersisting(); } - mTaskPersister.startPersisting(); // Check to see if there are any update receivers to run. if (!mDidUpdate) { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b948c41..b429b93 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1154,13 +1154,15 @@ final class ActivityRecord { } if (intent == null) { - Slog.e(TAG, "restoreActivity error intent=" + intent); - return null; + throw new XmlPullParserException("restoreActivity error intent=" + intent); } final ActivityManagerService service = stackSupervisor.mService; final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null, null, userId); + if (aInfo == null) { + throw new XmlPullParserException("restoreActivity resolver error."); + } final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid, launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(), null, null, 0, componentSpecified, stackSupervisor, null, null); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index d0ba118..a0440cb 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -37,6 +37,7 @@ import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP; import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_SCREENSHOTS; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; @@ -346,6 +347,10 @@ final class ActivityStack { mWindowManager = mService.mWindowManager; mStackId = activityContainer.mStackId; mCurrentUser = mService.mCurrentUserId; + // Get the activity screenshot thumbnail dimensions + Resources res = mService.mContext.getResources(); + mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); } /** @@ -729,42 +734,54 @@ final class ActivityStack { } } + /** + * This resets the saved state from the last screenshot, forcing a new screenshot to be taken + * again when requested. + */ + private void invalidateLastScreenshot() { + mLastScreenshotActivity = null; + if (mLastScreenshotBitmap != null) { + mLastScreenshotBitmap.recycle(); + } + mLastScreenshotBitmap = null; + } + public final Bitmap screenshotActivities(ActivityRecord who) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "screenshotActivities: " + who); if (who.noDisplay) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tNo display"); return null; } TaskRecord tr = who.task; - if (mService.getMostRecentTask() != tr && tr.intent != null && - (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) { - // If this task is being excluded from recents, we don't want to take - // the expense of capturing a thumbnail, since we will never show it. + if (mService.getMostRecentTask() != tr || isHomeStack()) { + // This is an optimization -- since we never show Home or Recents within Recents itself, + // we can just go ahead and skip taking the screenshot if this is the home stack. In + // the case where the most recent task is not the task that was supplied, then the stack + // has changed, so invalidate the last screenshot(). + invalidateLastScreenshot(); + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tIs Home stack? " + isHomeStack()); return null; } - Resources res = mService.mContext.getResources(); int w = mThumbnailWidth; int h = mThumbnailHeight; - if (w < 0) { - mThumbnailWidth = w = - res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_width); - mThumbnailHeight = h = - res.getDimensionPixelSize(com.android.internal.R.dimen.recents_thumbnail_height); - } - if (w > 0) { if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null || mLastScreenshotActivity.state == ActivityState.RESUMED || mLastScreenshotBitmap.getWidth() != w || mLastScreenshotBitmap.getHeight() != h) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tUpdating screenshot"); mLastScreenshotActivity = who; mLastScreenshotBitmap = mWindowManager.screenshotApplications( who.appToken, Display.DEFAULT_DISPLAY, w, h, SCREENSHOT_FORCE_565); } if (mLastScreenshotBitmap != null) { + if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tReusing last screenshot"); return mLastScreenshotBitmap.copy(mLastScreenshotBitmap.getConfig(), true); } } + Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h); return null; } @@ -1042,6 +1059,12 @@ final class ActivityStack { } else { next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process } + + // If we are resuming the activity that we had last screenshotted, then we know it will be + // updated, so invalidate the last screenshot to ensure we take a fresh one when requested + if (next == mLastScreenshotActivity) { + invalidateLastScreenshot(); + } } private void setVisibile(ActivityRecord r, boolean visible) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index c1a4643..ae7fab3 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -109,6 +109,7 @@ public final class ActivityStackSupervisor implements DisplayListener { static final boolean DEBUG_SAVED_STATE = DEBUG || false; static final boolean DEBUG_STATES = DEBUG || false; static final boolean DEBUG_IDLE = DEBUG || false; + static final boolean DEBUG_SCREENSHOTS = DEBUG || false; public static final int HOME_STACK_ID = 0; @@ -1212,8 +1213,7 @@ public final class ActivityStackSupervisor implements DisplayListener { requestCode = sourceRecord.requestCode; sourceRecord.resultTo = null; if (resultRecord != null) { - resultRecord.removeResultsLocked( - sourceRecord, resultWho, requestCode); + resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); } if (sourceRecord.launchedFromUid == callingUid) { // The new activity is being launched from the same uid as the previous @@ -1385,7 +1385,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return err; } - ActivityStack adjustStackFocus(ActivityRecord r) { + ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) { final TaskRecord task = r.task; if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) { if (task != null) { @@ -1410,7 +1410,8 @@ public final class ActivityStackSupervisor implements DisplayListener { return container.mStack; } - if (mFocusedStack != mHomeStack) { + if (mFocusedStack != mHomeStack && (!newTask || + mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Have a focused stack=" + mFocusedStack); return mFocusedStack; @@ -1463,7 +1464,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // We'll invoke onUserLeaving before onPause only if the launching // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving); // If the caller has asked not to resume at this point, we make note @@ -1473,7 +1474,8 @@ public final class ActivityStackSupervisor implements DisplayListener { r.delayedResume = true; } - ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; + ActivityRecord notTop = + (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; // If the onlyIfNeeded flag is set, then we can do this if the activity // being launched is the same as the one making the call... or, as @@ -1496,9 +1498,11 @@ public final class ActivityStackSupervisor implements DisplayListener { case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: intent.addFlags( Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + launchFlags = intent.getFlags(); break; case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + launchFlags = intent.getFlags(); break; } final boolean newDocument = intent.isDocument(); @@ -1804,7 +1808,8 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } - targetStack = adjustStackFocus(r); + newTask = true; + targetStack = adjustStackFocus(r, newTask); targetStack.moveToFront(); if (reuseTask == null) { r.setTask(targetStack.createTaskRecord(getNextTaskId(), @@ -1821,7 +1826,6 @@ public final class ActivityStackSupervisor implements DisplayListener { } else { r.setTask(reuseTask, reuseTask, true); } - newTask = true; if (!movedHome) { if ((launchFlags & (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) @@ -1889,7 +1893,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // This not being started from an existing activity, and not part // of a new task... just put it in the top task, though these days // this case should never happen. - targetStack = adjustStackFocus(r); + targetStack = adjustStackFocus(r, newTask); targetStack.moveToFront(); ActivityRecord prev = targetStack.topActivity(); r.setTask(prev != null ? prev.task @@ -2316,7 +2320,12 @@ public final class ActivityStackSupervisor implements DisplayListener { for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); if (!r.isApplicationActivity() && !stack.isHomeStack()) { - if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: " + stack); + if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (home activity) " + stack); + continue; + } + if (!stack.mActivityContainer.isEligibleForNewTasks()) { + if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (new task not allowed) " + + stack); continue; } final ActivityRecord ar = stack.findTaskLocked(r); @@ -3247,6 +3256,11 @@ public final class ActivityStackSupervisor implements DisplayListener { void setDrawn() { } + // You can always start a new task on a regular ActivityStack. + boolean isEligibleForNewTasks() { + return true; + } + @Override public String toString() { return mIdString + (mActivityDisplay == null ? "N" : "A"); @@ -3327,6 +3341,12 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + // Never start a new task on an ActivityView if it isn't explicitly specified. + @Override + boolean isEligibleForNewTasks() { + return false; + } + private void setSurfaceIfReady() { if (DEBUG_STACK) Slog.v(TAG, "setSurfaceIfReady: mDrawn=" + mDrawn + " mContainerState=" + mContainerState + " mSurface=" + mSurface); diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index ba3f2fe..3bfaca9 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -155,6 +155,7 @@ public class TaskPersister { File taskFile = recentFiles[taskNdx]; if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName()); BufferedReader reader = null; + boolean deleteFile = false; try { reader = new BufferedReader(new FileReader(taskFile)); final XmlPullParser in = Xml.newPullParser(); @@ -183,10 +184,9 @@ public class TaskPersister { } XmlUtils.skipCurrentTag(in); } - } catch (IOException e) { - Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e); - } catch (XmlPullParserException e) { - Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e); + } catch (Exception e) { + Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e); + deleteFile = true; } finally { if (reader != null) { try { @@ -194,6 +194,9 @@ public class TaskPersister { } catch (IOException e) { } } + if (!DEBUG && deleteFile) { + taskFile.delete(); + } } } @@ -220,7 +223,7 @@ public class TaskPersister { return new ArrayList<TaskRecord>(Arrays.asList(tasksArray)); } - private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { + private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) { File file = files[fileNdx]; String filename = file.getName(); @@ -285,8 +288,7 @@ public class TaskPersister { synchronized(mService) { final ArrayList<TaskRecord> tasks = mService.mRecentTasks; persistentTaskIds.clear(); - int taskNdx; - for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { task = tasks.get(taskNdx); if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + task.isPersistable + " needsPersisting=" + task.needsPersisting); diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index d81a25e..66cc532 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -46,12 +46,14 @@ import java.util.Map; * {@hide} */ public class NotificationUsageStats { + private static final boolean ENABLE_SQLITE_LOG = false; + // Guarded by synchronized(this). private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>(); private final SQLiteLog mSQLiteLog; public NotificationUsageStats(Context context) { - mSQLiteLog = new SQLiteLog(context); + mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null; } /** @@ -63,7 +65,9 @@ public class NotificationUsageStats { for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numPostedByApp++; } - mSQLiteLog.logPosted(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logPosted(notification); + } } /** @@ -85,7 +89,9 @@ public class NotificationUsageStats { stats.numRemovedByApp++; stats.collect(notification.stats); } - mSQLiteLog.logRemoved(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logRemoved(notification); + } } /** @@ -97,7 +103,9 @@ public class NotificationUsageStats { stats.numDismissedByUser++; stats.collect(notification.stats); } - mSQLiteLog.logDismissed(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logDismissed(notification); + } } /** @@ -108,7 +116,9 @@ public class NotificationUsageStats { for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numClickedByUser++; } - mSQLiteLog.logClicked(notification); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.logClicked(notification); + } } /** @@ -164,7 +174,9 @@ public class NotificationUsageStats { for (AggregatedStats as : mStats.values()) { as.dump(pw, indent); } - mSQLiteLog.dump(pw, indent); + if (ENABLE_SQLITE_LOG) { + mSQLiteLog.dump(pw, indent); + } } /** diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 157d749..a629a5f 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -139,56 +139,64 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } private String[] getExtraPeople(Bundle extras) { - String[] people = extras.getStringArray(Notification.EXTRA_PEOPLE); - if (people != null) { - return people; + Object people = extras.get(Notification.EXTRA_PEOPLE); + if (people instanceof String[]) { + return (String[]) people; } - ArrayList<String> stringArray = extras.getStringArrayList(Notification.EXTRA_PEOPLE); - if (stringArray != null) { - return (String[]) stringArray.toArray(); + if (people instanceof ArrayList) { + ArrayList arrayList = (ArrayList) people; + + if (arrayList.isEmpty()) { + return null; + } + + if (arrayList.get(0) instanceof String) { + ArrayList<String> stringArray = (ArrayList<String>) arrayList; + return stringArray.toArray(new String[stringArray.size()]); + } + + if (arrayList.get(0) instanceof CharSequence) { + ArrayList<CharSequence> charSeqList = (ArrayList<CharSequence>) arrayList; + final int N = charSeqList.size(); + String[] array = new String[N]; + for (int i = 0; i < N; i++) { + array[i] = charSeqList.get(i).toString(); + } + return array; + } + + return null; } - String string = extras.getString(Notification.EXTRA_PEOPLE); - if (string != null) { - people = new String[1]; - people[0] = string; - return people; + if (people instanceof String) { + String[] array = new String[1]; + array[0] = (String) people; + return array; } - char[] charArray = extras.getCharArray(Notification.EXTRA_PEOPLE); - if (charArray != null) { - people = new String[1]; - people[0] = new String(charArray); - return people; + + if (people instanceof char[]) { + String[] array = new String[1]; + array[0] = new String((char[]) people); + return array; } - CharSequence charSeq = extras.getCharSequence(Notification.EXTRA_PEOPLE); - if (charSeq != null) { - people = new String[1]; - people[0] = charSeq.toString(); - return people; + if (people instanceof CharSequence) { + String[] array = new String[1]; + array[0] = ((CharSequence) people).toString(); + return array; } - CharSequence[] charSeqArray = extras.getCharSequenceArray(Notification.EXTRA_PEOPLE); - if (charSeqArray != null) { + if (people instanceof CharSequence[]) { + CharSequence[] charSeqArray = (CharSequence[]) people; final int N = charSeqArray.length; - people = new String[N]; + String[] array = new String[N]; for (int i = 0; i < N; i++) { - people[i] = charSeqArray[i].toString(); + array[i] = charSeqArray[i].toString(); } - return people; + return array; } - ArrayList<CharSequence> charSeqList = - extras.getCharSequenceArrayList(Notification.EXTRA_PEOPLE); - if (charSeqList != null) { - final int N = charSeqList.size(); - people = new String[N]; - for (int i = 0; i < N; i++) { - people[i] = charSeqList.get(i).toString(); - } - return people; - } return null; } diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 47ce3b6..f18939f 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -63,6 +63,11 @@ public class TrustAgentWrapper { public void handleMessage(Message msg) { switch (msg.what) { case MSG_GRANT_TRUST: + if (!isConnected()) { + Log.w(TAG, "Agent is not connected, cannot grant trust: " + + mName.flattenToShortString()); + return; + } mTrusted = true; mMessage = (CharSequence) msg.obj; boolean initiatedByUser = msg.arg1 != 0; @@ -119,6 +124,7 @@ public class TrustAgentWrapper { public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Log.v(TAG, "TrustAgent started : " + name.flattenToString()); mTrustAgentService = ITrustAgentService.Stub.asInterface(service); + mTrustManagerService.mArchive.logAgentConnected(mUserId, name); setCallback(mCallback); } @@ -179,7 +185,10 @@ public class TrustAgentWrapper { public void unbind() { if (DEBUG) Log.v(TAG, "TrustAgent unbound : " + mName.flattenToShortString()); + mTrustManagerService.mArchive.logAgentStopped(mUserId, mName); mContext.unbindService(mConnection); + mTrustAgentService = null; + mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); } public boolean isConnected() { diff --git a/services/core/java/com/android/server/trust/TrustArchive.java b/services/core/java/com/android/server/trust/TrustArchive.java index aad156c..56950d2 100644 --- a/services/core/java/com/android/server/trust/TrustArchive.java +++ b/services/core/java/com/android/server/trust/TrustArchive.java @@ -33,6 +33,8 @@ public class TrustArchive { private static final int TYPE_REVOKE_TRUST = 1; private static final int TYPE_TRUST_TIMEOUT = 2; private static final int TYPE_AGENT_DIED = 3; + private static final int TYPE_AGENT_CONNECTED = 4; + private static final int TYPE_AGENT_STOPPED = 5; private static final int HISTORY_LIMIT = 200; @@ -79,6 +81,14 @@ public class TrustArchive { addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, false)); } + public void logAgentConnected(int userId, ComponentName agent) { + addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, false)); + } + + public void logAgentStopped(int userId, ComponentName agent) { + addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, false)); + } + private void addEvent(Event e) { if (mEvents.size() >= HISTORY_LIMIT) { mEvents.removeFirst(); @@ -152,6 +162,10 @@ public class TrustArchive { return "TrustTimeout"; case TYPE_AGENT_DIED: return "AgentDied"; + case TYPE_AGENT_CONNECTED: + return "AgentConnected"; + case TYPE_AGENT_STOPPED: + return "AgentStopped"; default: return "Unknown(" + type + ")"; } diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java new file mode 100644 index 0000000..4bdd2be --- /dev/null +++ b/services/core/java/com/android/server/tv/TvInputHal.java @@ -0,0 +1,128 @@ +/* + * Copyright 2014 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 com.android.server.tv; + +import android.os.Handler; +import android.os.HandlerThread; +import android.tv.TvInputHardwareInfo; +import android.tv.TvStreamConfig; +import android.view.Surface; + +/** + * Provides access to the low-level TV input hardware abstraction layer. + */ +final class TvInputHal { + public final static int SUCCESS = 0; + public final static int ERROR_NO_INIT = -1; + public final static int ERROR_STALE_CONFIG = -2; + public final static int ERROR_UNKNOWN = -3; + + public static final int TYPE_HDMI = 1; + public static final int TYPE_BUILT_IN_TUNER = 2; + public static final int TYPE_PASSTHROUGH = 3; + + public interface Callback { + public void onDeviceAvailable( + TvInputHardwareInfo info, TvStreamConfig[] configs); + public void onDeviceUnavailable(int deviceId); + public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs); + } + + private native long nativeOpen(); + + private static native int nativeSetSurface(long ptr, int deviceId, int streamId, + Surface surface); + private static native TvStreamConfig[] nativeGetStreamConfigs(long ptr, int deviceId, + int generation); + private static native void nativeClose(long ptr); + + private long mPtr = 0l; + private final Callback mCallback; + private final HandlerThread mThread = new HandlerThread("TV input HAL event thread"); + private final Handler mHandler; + private int mStreamConfigGeneration = 0; + private TvStreamConfig[] mStreamConfigs; + + public TvInputHal(Callback callback) { + mCallback = callback; + mThread.start(); + mHandler = new Handler(mThread.getLooper()); + } + + public void init() { + mPtr = nativeOpen(); + } + + public int setSurface(int deviceId, Surface surface, TvStreamConfig streamConfig) { + if (mPtr == 0) { + return ERROR_NO_INIT; + } + if (mStreamConfigGeneration != streamConfig.getGeneration()) { + return ERROR_STALE_CONFIG; + } + if (nativeSetSurface(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) { + return SUCCESS; + } else { + return ERROR_UNKNOWN; + } + } + + public void close() { + if (mPtr != 0l) { + nativeClose(mPtr); + mThread.quitSafely(); + } + } + + private synchronized void retrieveStreamConfigs(int deviceId) { + ++mStreamConfigGeneration; + mStreamConfigs = nativeGetStreamConfigs(mPtr, deviceId, mStreamConfigGeneration); + } + + // Called from native + private void deviceAvailableFromNative(int deviceId, int type) { + final TvInputHardwareInfo info = new TvInputHardwareInfo(deviceId, type); + mHandler.post(new Runnable() { + @Override + public void run() { + retrieveStreamConfigs(info.getDeviceId()); + mCallback.onDeviceAvailable(info, mStreamConfigs); + } + }); + } + + private void deviceUnavailableFromNative(int deviceId) { + final int id = deviceId; + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onDeviceUnavailable(id); + } + }); + } + + private void streamConfigsChangedFromNative(int deviceId) { + final int id = deviceId; + mHandler.post(new Runnable() { + @Override + public void run() { + retrieveStreamConfigs(id); + mCallback.onStreamConfigurationChanged(id, mStreamConfigs); + } + }); + } +} diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java new file mode 100644 index 0000000..b95b0f0 --- /dev/null +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2014 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 com.android.server.tv; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.tv.ITvInputHardware; +import android.tv.ITvInputHardwareCallback; +import android.tv.TvInputHardwareInfo; +import android.tv.TvStreamConfig; +import android.util.Slog; +import android.util.SparseArray; +import android.view.KeyEvent; +import android.view.Surface; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A helper class for TvInputManagerService to handle TV input hardware. + * + * This class does a basic connection management and forwarding calls to TvInputHal which eventually + * calls to tv_input HAL module. + * + * @hide + */ +class TvInputHardwareManager implements TvInputHal.Callback { + private static final String TAG = TvInputHardwareManager.class.getSimpleName(); + private final TvInputHal mHal = new TvInputHal(this); + private final SparseArray<Connection> mConnections = new SparseArray<Connection>(); + private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>(); + private final Context mContext; + private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); + + private final Object mLock = new Object(); + + public TvInputHardwareManager(Context context) { + mContext = context; + // TODO(hdmi): mHdmiManager = mContext.getSystemService(...); + // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient(); + mHal.init(); + } + + @Override + public void onDeviceAvailable( + TvInputHardwareInfo info, TvStreamConfig[] configs) { + synchronized (mLock) { + Connection connection = new Connection(info); + connection.updateConfigsLocked(configs); + mConnections.put(info.getDeviceId(), connection); + buildInfoListLocked(); + // TODO: notify if necessary + } + } + + private void buildInfoListLocked() { + mInfoList.clear(); + for (int i = 0; i < mConnections.size(); ++i) { + mInfoList.add(mConnections.valueAt(i).getInfoLocked()); + } + } + + @Override + public void onDeviceUnavailable(int deviceId) { + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); + return; + } + connection.resetLocked(null, null, null, null); + mConnections.remove(deviceId); + buildInfoListLocked(); + // TODO: notify if necessary + } + } + + @Override + public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " + + deviceId); + return; + } + connection.updateConfigsLocked(configs); + try { + connection.getCallbackLocked().onStreamConfigChanged(configs); + } catch (RemoteException e) { + Slog.e(TAG, "onStreamConfigurationChanged: " + e); + } + } + } + + public List<TvInputHardwareInfo> getHardwareList() { + synchronized (mLock) { + return mInfoList; + } + } + + /** + * Create a TvInputHardware object with a specific deviceId. One service at a time can access + * the object, and if more than one process attempts to create hardware with the same deviceId, + * the latest service will get the object and all the other hardware are released. The + * release is notified via ITvInputHardwareCallback.onReleased(). + */ + public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, + int callingUid, int resolvedUserId) { + if (callback == null) { + throw new NullPointerException(); + } + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "Invalid deviceId : " + deviceId); + return null; + } + if (connection.getCallingUidLocked() != callingUid + || connection.getResolvedUserIdLocked() != resolvedUserId) { + TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked()); + try { + callback.asBinder().linkToDeath(connection, 0); + } catch (RemoteException e) { + hardware.release(); + return null; + } + connection.resetLocked(hardware, callback, callingUid, resolvedUserId); + } + return connection.getHardwareLocked(); + } + } + + /** + * Release the specified hardware. + */ + public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, + int resolvedUserId) { + synchronized (mLock) { + Connection connection = mConnections.get(deviceId); + if (connection == null) { + Slog.e(TAG, "Invalid deviceId : " + deviceId); + return; + } + if (connection.getHardwareLocked() != hardware + || connection.getCallingUidLocked() != callingUid + || connection.getResolvedUserIdLocked() != resolvedUserId) { + return; + } + connection.resetLocked(null, null, null, null); + } + } + + private class Connection implements IBinder.DeathRecipient { + private final TvInputHardwareInfo mInfo; + private TvInputHardwareImpl mHardware = null; + private ITvInputHardwareCallback mCallback; + private TvStreamConfig[] mConfigs = null; + private Integer mCallingUid = null; + private Integer mResolvedUserId = null; + + public Connection(TvInputHardwareInfo info) { + mInfo = info; + } + + // *Locked methods assume TvInputHardwareManager.mLock is held. + + public void resetLocked(TvInputHardwareImpl hardware, + ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) { + if (mHardware != null) { + try { + mCallback.onReleased(); + } catch (RemoteException e) { + Slog.e(TAG, "Connection::resetHardware: " + e); + } + mHardware.release(); + } + mHardware = hardware; + mCallback = callback; + mCallingUid = callingUid; + mResolvedUserId = resolvedUserId; + + if (mHardware != null && mCallback != null) { + try { + mCallback.onStreamConfigChanged(getConfigsLocked()); + } catch (RemoteException e) { + Slog.e(TAG, "Connection::resetHardware: " + e); + } + } + } + + public void updateConfigsLocked(TvStreamConfig[] configs) { + mConfigs = configs; + } + + public TvInputHardwareInfo getInfoLocked() { + return mInfo; + } + + public ITvInputHardware getHardwareLocked() { + return mHardware; + } + + public ITvInputHardwareCallback getCallbackLocked() { + return mCallback; + } + + public TvStreamConfig[] getConfigsLocked() { + return mConfigs; + } + + public int getCallingUidLocked() { + return mCallingUid; + } + + public int getResolvedUserIdLocked() { + return mResolvedUserId; + } + + @Override + public void binderDied() { + synchronized (mLock) { + resetLocked(null, null, null, null); + } + } + } + + private class TvInputHardwareImpl extends ITvInputHardware.Stub { + private final TvInputHardwareInfo mInfo; + private boolean mReleased = false; + private final Object mImplLock = new Object(); + + public TvInputHardwareImpl(TvInputHardwareInfo info) { + mInfo = info; + } + + public void release() { + synchronized (mImplLock) { + mReleased = true; + } + } + + @Override + public boolean setSurface(Surface surface, TvStreamConfig config) + throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + if (mInfo.getType() == TvInputHal.TYPE_HDMI) { + if (surface != null) { + // Set "Active Source" for HDMI. + // TODO(hdmi): mHdmiClient.deviceSelect(...); + mActiveHdmiSources.add(mInfo.getDeviceId()); + } else { + mActiveHdmiSources.remove(mInfo.getDeviceId()); + if (mActiveHdmiSources.size() == 0) { + // Tell HDMI that no HDMI source is active + // TODO(hdmi): mHdmiClient.portSelect(null); + } + } + } + return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS; + } + } + + @Override + public void setVolume(float volume) throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + } + // TODO + } + + @Override + public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + } + if (mInfo.getType() != TvInputHal.TYPE_HDMI) { + return false; + } + // TODO(hdmi): mHdmiClient.sendKeyEvent(event); + return false; + } + } +} diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 8ad7fff..6c38a4c 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -42,11 +42,14 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.TvContract; import android.tv.ITvInputClient; +import android.tv.ITvInputHardware; +import android.tv.ITvInputHardwareCallback; import android.tv.ITvInputManager; import android.tv.ITvInputService; import android.tv.ITvInputServiceCallback; import android.tv.ITvInputSession; import android.tv.ITvInputSessionCallback; +import android.tv.TvInputHardwareInfo; import android.tv.TvInputInfo; import android.tv.TvInputService; import android.util.Slog; @@ -71,6 +74,7 @@ public final class TvInputManagerService extends SystemService { private static final String TAG = "TvInputManagerService"; private final Context mContext; + private final TvInputHardwareManager mTvInputHardwareManager; private final ContentResolver mContentResolver; @@ -92,6 +96,7 @@ public final class TvInputManagerService extends SystemService { mContentResolver = context.getContentResolver(); mLogHandler = new LogHandler(IoThread.get().getLooper()); + mTvInputHardwareManager = new TvInputHardwareManager(context); registerBroadcastReceivers(); synchronized (mLock) { @@ -730,6 +735,64 @@ public final class TvInputManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public List<TvInputHardwareInfo> getHardwareList() throws RemoteException { + if (mContext.checkCallingPermission( + android.Manifest.permission.TV_INPUT_HARDWARE) + != PackageManager.PERMISSION_GRANTED) { + return null; + } + + final long identity = Binder.clearCallingIdentity(); + try { + return mTvInputHardwareManager.getHardwareList(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public ITvInputHardware acquireTvInputHardware(int deviceId, + ITvInputHardwareCallback callback, int userId) throws RemoteException { + if (mContext.checkCallingPermission( + android.Manifest.permission.TV_INPUT_HARDWARE) + != PackageManager.PERMISSION_GRANTED) { + return null; + } + + final long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "acquireTvInputHardware"); + try { + return mTvInputHardwareManager.acquireHardware( + deviceId, callback, callingUid, resolvedUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId) + throws RemoteException { + if (mContext.checkCallingPermission( + android.Manifest.permission.TV_INPUT_HARDWARE) + != PackageManager.PERMISSION_GRANTED) { + return; + } + + final long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "releaseTvInputHardware"); + try { + mTvInputHardwareManager.releaseHardware( + deviceId, hardware, callingUid, resolvedUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } private static final class UserState { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6fbb39d..c23d1ea 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3189,6 +3189,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = atoken.findMainWindow(); Rect containingFrame = new Rect(0, 0, width, height); Rect contentInsets = new Rect(); + boolean isFullScreen = true; if (win != null) { if (win.mContainingFrame != null) { containingFrame.set(win.mContainingFrame); @@ -3196,11 +3197,11 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mContentInsets != null) { contentInsets.set(win.mContentInsets); } + isFullScreen = + ((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) == + SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN); } - boolean isFullScreen = - ((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) - == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN); Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height, mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen); if (a != null) { diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 51583a5..3cfb45b 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -23,6 +23,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \ + $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \ diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp new file mode 100644 index 0000000..f0c4f3a --- /dev/null +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -0,0 +1,388 @@ +/* + * Copyright 2014 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. + */ + +#define LOG_TAG "TvInputHal" + +//#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/android_view_Surface.h" +#include "JNIHelp.h" +#include "jni.h" + +#include <gui/Surface.h> +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/Log.h> +#include <utils/NativeHandle.h> +#include <hardware/tv_input.h> + +namespace android { + +static struct { + jmethodID deviceAvailable; + jmethodID deviceUnavailable; + jmethodID streamConfigsChanged; +} gTvInputHalClassInfo; + +static struct { + jclass clazz; +} gTvStreamConfigClassInfo; + +static struct { + jclass clazz; + + jmethodID constructor; + jmethodID streamId; + jmethodID type; + jmethodID maxWidth; + jmethodID maxHeight; + jmethodID generation; + jmethodID build; +} gTvStreamConfigBuilderClassInfo; + +//////////////////////////////////////////////////////////////////////////////// + +class JTvInputHal { +public: + ~JTvInputHal(); + + static JTvInputHal* createInstance(JNIEnv* env, jobject thiz); + + int setSurface(int deviceId, int streamId, const sp<Surface>& surface); + void getStreamConfigs(int deviceId, jobjectArray* array); + const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs); + +private: + class Connection { + public: + Connection() : mStreamId(0) {} + + sp<Surface> mSurface; + sp<NativeHandle> mSourceHandle; + int mStreamId; + }; + + JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev); + + static void notify( + tv_input_device_t* dev,tv_input_event_t* event, void* data); + + void onDeviceAvailable(const tv_input_device_info_t& info); + void onDeviceUnavailable(int deviceId); + void onStreamConfigurationsChanged(int deviceId); + + jweak mThiz; + tv_input_device_t* mDevice; + tv_input_callback_ops_t mCallback; + + KeyedVector<int, Connection> mConnections; +}; + +JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device) { + mThiz = env->NewWeakGlobalRef(thiz); + mDevice = device; + mCallback.notify = &JTvInputHal::notify; + + mDevice->initialize(mDevice, &mCallback, this); +} + +JTvInputHal::~JTvInputHal() { + mDevice->common.close((hw_device_t*)mDevice); + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mThiz); + mThiz = NULL; +} + +JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz) { + tv_input_module_t* module = NULL; + status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID, + (hw_module_t const**)&module); + if (err) { + ALOGE("Couldn't load %s module (%s)", + TV_INPUT_HARDWARE_MODULE_ID, strerror(-err)); + return 0; + } + + tv_input_device_t* device = NULL; + err = module->common.methods->open( + (hw_module_t*)module, + TV_INPUT_DEFAULT_DEVICE, + (hw_device_t**)&device); + if (err) { + ALOGE("Couldn't open %s device (%s)", + TV_INPUT_DEFAULT_DEVICE, strerror(-err)); + return 0; + } + + return new JTvInputHal(env, thiz, device); +} + +int JTvInputHal::setSurface(int deviceId, int streamId, const sp<Surface>& surface) { + Connection& connection = mConnections.editValueFor(deviceId); + if (connection.mStreamId == streamId && connection.mSurface == surface) { + // Nothing to do + return NO_ERROR; + } + if (Surface::isValid(connection.mSurface)) { + connection.mSurface.clear(); + } + if (surface == NULL) { + if (connection.mSurface != NULL) { + connection.mSurface->setSidebandStream(NULL); + connection.mSurface.clear(); + } + if (connection.mSourceHandle != NULL) { + // Need to reset streams + if (mDevice->close_stream( + mDevice, deviceId, connection.mStreamId) != 0) { + ALOGE("Couldn't remove stream"); + return BAD_VALUE; + } + connection.mSourceHandle.clear(); + } + return NO_ERROR; + } + connection.mSurface = surface; + if (connection.mSourceHandle == NULL) { + // Need to configure stream + int numConfigs = 0; + const tv_stream_config_t* configs = NULL; + if (mDevice->get_stream_configurations( + mDevice, deviceId, &numConfigs, &configs) != 0) { + ALOGE("Couldn't get stream configs"); + return UNKNOWN_ERROR; + } + int configIndex = -1; + for (int i = 0; i < numConfigs; ++i) { + if (configs[i].stream_id == streamId) { + configIndex = i; + break; + } + } + if (configIndex == -1) { + ALOGE("Cannot find a config with given stream ID: %d", streamId); + return BAD_VALUE; + } + // TODO: handle buffer producer profile. + if (configs[configIndex].type != + TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { + ALOGE("Profiles other than independent video source is not yet " + "supported : type = %d", configs[configIndex].type); + return INVALID_OPERATION; + } + tv_stream_t stream; + stream.stream_id = configs[configIndex].stream_id; + if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) { + ALOGE("Couldn't add stream"); + return UNKNOWN_ERROR; + } + connection.mSourceHandle = NativeHandle::create( + stream.sideband_stream_source_handle, false); + connection.mStreamId = stream.stream_id; + connection.mSurface->setSidebandStream(connection.mSourceHandle); + } + return NO_ERROR; +} + +const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) { + const tv_stream_config_t* configs = NULL; + if (mDevice->get_stream_configurations( + mDevice, deviceId, numConfigs, &configs) != 0) { + ALOGE("Couldn't get stream configs"); + return NULL; + } + return configs; +} + + +// static +void JTvInputHal::notify( + tv_input_device_t* dev, tv_input_event_t* event, void* data) { + JTvInputHal* thiz = (JTvInputHal*)data; + switch (event->type) { + case TV_INPUT_EVENT_DEVICE_AVAILABLE: { + thiz->onDeviceAvailable(event->device_info); + } break; + case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: { + thiz->onDeviceUnavailable(event->device_info.device_id); + } break; + case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: { + thiz->onStreamConfigurationsChanged(event->device_info.device_id); + } break; + default: + ALOGE("Unrecognizable event"); + } +} + +void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mConnections.add(info.device_id, Connection()); + env->CallVoidMethod( + mThiz, + gTvInputHalClassInfo.deviceAvailable, + info.device_id, + info.type); +} + +void JTvInputHal::onDeviceUnavailable(int deviceId) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mConnections.removeItem(deviceId); + env->CallVoidMethod( + mThiz, + gTvInputHalClassInfo.deviceUnavailable, + deviceId); +} + +void JTvInputHal::onStreamConfigurationsChanged(int deviceId) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mConnections.removeItem(deviceId); + env->CallVoidMethod( + mThiz, + gTvInputHalClassInfo.streamConfigsChanged, + deviceId); +} + +//////////////////////////////////////////////////////////////////////////////// + +static jlong nativeOpen(JNIEnv* env, jobject thiz) { + return (jlong)JTvInputHal::createInstance(env, thiz); +} + +static int nativeSetSurface(JNIEnv* env, jclass clazz, + jlong ptr, jint deviceId, jint streamId, jobject jsurface) { + JTvInputHal* tvInputHal = (JTvInputHal*)ptr; + sp<Surface> surface( + jsurface + ? android_view_Surface_getSurface(env, jsurface) + : NULL); + return tvInputHal->setSurface(deviceId, streamId, surface); +} + +static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz, + jlong ptr, jint deviceId, jint generation) { + JTvInputHal* tvInputHal = (JTvInputHal*)ptr; + int numConfigs = 0; + const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs); + + jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL); + for (int i = 0; i < numConfigs; ++i) { + jobject builder = env->NewObject( + gTvStreamConfigBuilderClassInfo.clazz, + gTvStreamConfigBuilderClassInfo.constructor); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.generation, generation); + + jobject config = env->CallObjectMethod(builder, gTvStreamConfigBuilderClassInfo.build); + + env->SetObjectArrayElement(result, i, config); + + env->DeleteLocalRef(config); + env->DeleteLocalRef(builder); + } + return result; +} + +static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { + JTvInputHal* tvInputHal = (JTvInputHal*)ptr; + delete tvInputHal; +} + +static JNINativeMethod gTvInputHalMethods[] = { + /* name, signature, funcPtr */ + { "nativeOpen", "()J", + (void*) nativeOpen }, + { "nativeSetSurface", "(JIILandroid/view/Surface;)I", + (void*) nativeSetSurface }, + { "nativeGetStreamConfigs", "(JII)[Landroid/tv/TvStreamConfig;", + (void*) nativeGetStreamConfigs }, + { "nativeClose", "(J)V", + (void*) nativeClose }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className) + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method" methodName) + +int register_android_server_tv_TvInputHal(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal", + gTvInputHalMethods, NELEM(gTvInputHalMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass clazz; + FIND_CLASS(clazz, "com/android/server/tv/TvInputHal"); + + GET_METHOD_ID( + gTvInputHalClassInfo.deviceAvailable, clazz, "deviceAvailableFromNative", "(II)V"); + GET_METHOD_ID( + gTvInputHalClassInfo.deviceUnavailable, clazz, "deviceUnavailableFromNative", "(I)V"); + GET_METHOD_ID( + gTvInputHalClassInfo.streamConfigsChanged, clazz, + "streamConfigsChangedFromNative", "(I)V"); + + FIND_CLASS(gTvStreamConfigClassInfo.clazz, "android/tv/TvStreamConfig"); + gTvStreamConfigClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigClassInfo.clazz)); + + FIND_CLASS(gTvStreamConfigBuilderClassInfo.clazz, "android/tv/TvStreamConfig$Builder"); + gTvStreamConfigBuilderClassInfo.clazz = + jclass(env->NewGlobalRef(gTvStreamConfigBuilderClassInfo.clazz)); + + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.constructor, + gTvStreamConfigBuilderClassInfo.clazz, + "<init>", "()V"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.streamId, + gTvStreamConfigBuilderClassInfo.clazz, + "streamId", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.type, + gTvStreamConfigBuilderClassInfo.clazz, + "type", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.maxWidth, + gTvStreamConfigBuilderClassInfo.clazz, + "maxWidth", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.maxHeight, + gTvStreamConfigBuilderClassInfo.clazz, + "maxHeight", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.generation, + gTvStreamConfigBuilderClassInfo.clazz, + "generation", "(I)Landroid/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.build, + gTvStreamConfigBuilderClassInfo.clazz, + "build", "()Landroid/tv/TvStreamConfig;"); + + return 0; +} + +} /* namespace android */ diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 1feb325..bfa8286 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -41,6 +41,7 @@ int register_android_server_dreams_McuHal(JNIEnv* env); int register_android_server_hdmi_HdmiCecController(JNIEnv* env); int register_android_server_hdmi_HdmiCecService(JNIEnv* env); int register_android_server_hdmi_HdmiMhlController(JNIEnv* env); +int register_android_server_tv_TvInputHal(JNIEnv* env); }; using namespace android; @@ -78,6 +79,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) // TODO: remove this once replaces HdmiCecService with HdmiControlService. register_android_server_hdmi_HdmiCecService(env); register_android_server_hdmi_HdmiMhlController(env); + register_android_server_tv_TvInputHal(env); return JNI_VERSION_1_4; } |