summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManagerNative.java18
-rw-r--r--core/java/android/app/IActivityManager.java3
-rw-r--r--core/java/com/android/server/backup/SystemBackupAgent.java10
-rw-r--r--core/res/res/values/dimens_material.xml2
-rw-r--r--docs/html/tools/publishing/app-signing-eclipse.jd64
-rw-r--r--docs/html/tools/publishing/app-signing.jd49
-rw-r--r--docs/html/tools/publishing/preparing.jd58
-rw-r--r--docs/html/tools/publishing/publishing_overview.jd8
-rw-r--r--docs/html/tools/publishing/versioning.jd7
-rw-r--r--docs/html/tools/tools_toc.cs12
-rw-r--r--graphics/java/android/graphics/drawable/DrawableContainer.java15
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java4
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityManagerService.java30
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java26
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java3
-rw-r--r--services/core/java/com/android/server/am/TaskPersister.java581
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java52
17 files changed, 767 insertions, 175 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 06a26ec..e8d08b8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2363,6 +2363,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
+
+ case SYSTEM_BACKUP_RESTORED: {
+ data.enforceInterface(IActivityManager.descriptor);
+ systemBackupRestored();
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5458,5 +5465,16 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ @Override
+ public void systemBackupRestored() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(SYSTEM_BACKUP_RESTORED, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1ccbd27..e505d69 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -469,6 +469,8 @@ public interface IActivityManager extends IInterface {
public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
public void notifyEnterAnimationComplete(IBinder token) throws RemoteException;
+ public void systemBackupRestored() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -790,4 +792,5 @@ public interface IActivityManager extends IInterface {
int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240;
int CHECK_PERMISSION_WITH_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+241;
int REGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+242;
+ int SYSTEM_BACKUP_RESTORED = IBinder.FIRST_CALL_TRANSACTION+243;
}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index ed7ce63..35a1a5a 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
+import android.app.ActivityManagerNative;
import android.app.IWallpaperManager;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -186,4 +187,13 @@ public class SystemBackupAgent extends BackupAgentHelper {
}
}
}
+
+ @Override
+ public void onRestoreFinished() {
+ try {
+ ActivityManagerNative.getDefault().systemBackupRestored();
+ } catch (RemoteException e) {
+ // Not possible since this code is running in the system process.
+ }
+ }
}
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index dd1f815..8cc2a8a 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -89,7 +89,7 @@
<!-- Elevation when button is pressed -->
<dimen name="button_elevation_material">4dp</dimen>
<!-- Z translation to apply when button is pressed -->
- <dimen name="button_pressed_z_material">6dp</dimen>
+ <dimen name="button_pressed_z_material">2dp</dimen>
<!-- Default insets (outer padding) around buttons -->
<dimen name="button_inset_vertical_material">6dp</dimen>
<dimen name="button_inset_horizontal_material">@dimen/control_inset_material</dimen>
diff --git a/docs/html/tools/publishing/app-signing-eclipse.jd b/docs/html/tools/publishing/app-signing-eclipse.jd
new file mode 100644
index 0000000..738e488
--- /dev/null
+++ b/docs/html/tools/publishing/app-signing-eclipse.jd
@@ -0,0 +1,64 @@
+page.title=Signing Your Applications from Eclipse with ADT
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+
+<ol>
+<li><a href="#signing">Signing Your App for Release</a></li>
+</ol>
+
+<h2>See also</h2>
+
+<ol>
+<li><a href="{@docRoot}tools/publishing/app-signing.html">Signing Your Applications</a></li>
+</ol>
+
+</div>
+</div>
+
+
+<p>Android requires that all apps be digitally signed with a certificate before they can be
+installed. Android uses this certificate to identify the author of an app, and the certificate
+does not need to be signed by a certificate authority.</p>
+
+<p>This document provides detailed instructions about how to sign your apps in release mode with the
+ADT plugin for Eclipse. For information about how to obtain a certificate for signing your app, see
+<a href="{@docRoot}tools/publishing/app-signing.html">Signing Your Applications</a>.
+</p>
+
+
+<h2 id="signing">Signing Your App for Release</h2>
+
+<p>To sign your app for release with ADT, follow these steps:</p>
+
+<ol>
+<li>Select the project in the Package Explorer and select <strong>File</strong> >
+<strong>Export</strong>.</li>
+<li>On the <em>Export</em> window, select <strong>Export Android Application</strong> and click
+<strong>Next</strong>.</li>
+<li>On the <em>Export Android Application</em> window, select the project you want to sign and
+click <strong>Next</strong>.</li>
+<li>
+<p>On the next window, enter the location to create a keystore and a keystore password. If you
+already have a keystore, select <strong>Use existing keystore</strong>, enter your keystore's
+location and password, and go to step 6.</p>
+<img src="{@docRoot}images/tools/signadt3.png" alt=""
+ width="488" height="270" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 6</strong>. Select a keystore in ADT.</p>
+</li>
+<li><p>On the next window, provide the required information as shown in figure 5.<p>
+<p>Your key should be valid for at least 25 years, so you can sign app updates with the same key
+through the lifespan of your app.</p>
+<img src="{@docRoot}images/tools/signadt4.png" alt=""
+ width="488" height="448" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 7</strong>. Create a private key in ADT.</p>
+</li>
+<li><p>On the next window, select the location to export the signed APK.</p>
+<img src="{@docRoot}images/tools/signadt5.png" alt=""
+ width="488" height="217" style="margin-top:15px"/>
+<p class="img-caption"><strong>Figure 8</strong>. Export the signed APK in ADT.</p>
+</li>
+</ol>
diff --git a/docs/html/tools/publishing/app-signing.jd b/docs/html/tools/publishing/app-signing.jd
index 53e6e65..04e12cc 100644
--- a/docs/html/tools/publishing/app-signing.jd
+++ b/docs/html/tools/publishing/app-signing.jd
@@ -19,7 +19,7 @@ page.title=Signing Your Applications
<li><a href="sign-auto">Automatically Signing Your App</a></li>
</ol>
</li>
-<li><a href="#adt">Signing Your App with the ADT plugin for Eclipse</a></li>
+<li><a href="#studio">Signing Your App with Android Studio</a></li>
<li><a href="#considerations">Signing Considerations</a></li>
<li><a href="#secure-key">Securing Your Private Key</a></li>
<li><a href="#expdebug">Expiry of the Debug Certificate</a></li>
@@ -56,13 +56,19 @@ your own certificate.</p>
This certificate has a private key with a known password, so you can run and debug your app
without typing the password every time you make a change to your project.</p>
-<p>Android Studio and the ADT plugin for Eclipse sign your app in debug mode automatically when
+<p>Android Studio signs your app in debug mode automatically when
you run or debug your project from the IDE.</p>
<p>You can run and debug an app signed in debug mode on the emulator and on devices connected
to your development manchine through USB, but you cannot distribute an app signed in debug
mode.</p>
+<p>By default, the <em>debug</em> configuration uses a debug keystore, with a known
+password and a default key with a known password.
+The debug keystore is located in $HOME/.android/debug.keystore, and is created if not present.
+
+The debug build type is set to use this debug <code>SigningConfig</code> automatically. </p>
+
<p>For more information about how to build and run apps in debug mode, see
<a href="{@docRoot}tools/building/index.html">Building and Running</a>.</p>
@@ -88,7 +94,7 @@ any updates to your app, since you must always sign all versions of your app wit
key.</p>
<p>The rest of this document provides detailed instructions about how to generate a private
-key and sign your apps in release mode with Android Studio and with the ADT plugin for Eclipse.</p>
+key and sign your apps in release mode with Android Studio.</p>
<h3 id="wear-apps">Signing Android Wear Apps</h3>
@@ -154,41 +160,8 @@ more than one), and enter the required information.</p>
</ol>
<p>You can also specify your signing settings in Gradle configuration files. For more information,
-see <a href="{@docRoot}sdk/installing/studio-build.html#configureSigning">Signing settings</a>.</p>
-
+see <a href="{@docRoot}tools/gradle/studio-build.html#configureSigning">Signing settings</a>.</p>
-<h2 id="adt">Signing Your App with the ADT Plugin for Eclipse</h2>
-
-<p>To sign your app in release mode in ADT, follow these steps:</p>
-
-<ol>
-<li>Select the project in the Package Explorer and select <strong>File</strong> >
-<strong>Export</strong>.</li>
-<li>On the <em>Export</em> window, select <strong>Export Android Application</strong> and click
-<strong>Next</strong>.</li>
-<li>On the <em>Export Android Application</em> window, select the project you want to sign and
-click <strong>Next</strong>.</li>
-<li>
-<p>On the next window, enter the location to create a keystore and a keystore password. If you
-already have a keystore, select <strong>Use existing keystore</strong>, enter your keystore's
-location and password, and go to step 6.</p>
-<img src="{@docRoot}images/tools/signadt3.png" alt=""
- width="488" height="270" style="margin-top:15px"/>
-<p class="img-caption"><strong>Figure 6</strong>. Select a keystore in ADT.</p>
-</li>
-<li><p>On the next window, provide the required information as shown in figure 5.<p>
-<p>Your key should be valid for at least 25 years, so you can sign app updates with the same key
-through the lifespan of your app.</p>
-<img src="{@docRoot}images/tools/signadt4.png" alt=""
- width="488" height="448" style="margin-top:15px"/>
-<p class="img-caption"><strong>Figure 7</strong>. Create a private key in ADT.</p>
-</li>
-<li><p>On the next window, select the location to export the signed APK.</p>
-<img src="{@docRoot}images/tools/signadt5.png" alt=""
- width="488" height="217" style="margin-top:15px"/>
-<p class="img-caption"><strong>Figure 8</strong>. Export the signed APK in ADT.</p>
-</li>
-</ol>
<h2 id="considerations">Signing Considerations</h2>
@@ -272,7 +245,7 @@ because the build tools generated an expired debug certificate</a>.</p>
<h2 id="signing-manually">Signing Your App Manually</h2>
-<p>You do not need Android Studio or the ADT plugin for Eclipse to sign your app. You can sign
+<p>You do not need Android Studio to sign your app. You can sign
your app from the command line using standard tools from the Android SDK and the JDK. To sign
an app in release mode from the command line:</p>
diff --git a/docs/html/tools/publishing/preparing.jd b/docs/html/tools/publishing/preparing.jd
index 5265fce..b6b10fb 100644
--- a/docs/html/tools/publishing/preparing.jd
+++ b/docs/html/tools/publishing/preparing.jd
@@ -82,10 +82,10 @@ and it is optimized with the zipalign tool.</p>
</div>
<p>The signing and optimization tasks are usually seamless if you are building your application with
-Eclipse and the ADT plugin or with the Ant build script (included with the Android SDK). For
-example, you can use the Eclipse Export Wizard to compile, sign, and optimize your application all
-at once. You can also configure the Ant build script to do the same when you build from the command
-line.</p>
+Android Studio. For example, you can use Android Studio with the Gradle build files to compile, sign,
+and optimize your application all at once. You can also configure the Gradle build files to do the
+same when you build from the command line. For more details about using the Gradle build files, see
+<a href="{@docRoot}tools/gradle/studio-build.html">Building Your Project with Gradle</a>.</p>
<p>To prepare your application for release you typically perform five main tasks (see figure 2).
Each main task may include one or more smaller tasks depending on how you are releasing your
@@ -114,8 +114,8 @@ developer holds the private key). The Android system uses the certificate as a m
the author of an application and establishing trust relationships between applications. The
certificate that you use for signing does not need to be signed by a certificate authority; the
Android system allows you to sign your applications with a self-signed certificate. To learn about
-certificate requirements, see <a href="{@docRoot}tools/publishing/app-signing.html#cert">Obtain a
-suitable private key</a>.</p>
+certificate requirements, see <a href="{@docRoot}tools/publishing/app-signing.html">Signing Your
+Applications</a>.</p>
<p class="caution"><strong>Important:</strong> Your application must be signed with a cryptographic
key whose validity period ends after 22 October 2033.</p>
@@ -226,9 +226,9 @@ tasks:</p>
release.</li>
</ul>
-<h4>Review and update your manifest settings</h4>
+<h4>Review and update your manifest and Gradle build settings</h4>
-<p>Verify that the following manifest items are set correctly:</p>
+<p>Verify that the following manifest and build files items are set correctly:</p>
<ul>
<li><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">
@@ -248,7 +248,7 @@ tasks:</p>
</li>
</ul>
-<p>There are several additional manifest elements that you can set if you are releasing your
+<p>There are several additional manifest or build file elements that you can set if you are releasing your
application on Google Play. For example, the <code>android:minSdkVersion</code> and
<code>android:targetSdkVersion</code> attributes, which are located in the <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"> &lt;uses-sdk&gt;</a> element. For more
@@ -303,38 +303,24 @@ application, see <a href="{@docRoot}google/play/licensing/index.html">Applicatio
<h2 id="publishing-build">Building Your Application for Release</h2>
<p>After you finish configuring your application you can build it into a release-ready
-<code>.apk</code> fle that is signed and optimized. The JDK includes the tools for signing the
+<code>.apk</code> file that is signed and optimized. The JDK includes the tools for signing the
<code>.apk</code> file (Keytool and Jarsigner); the Android SDK includes the tools for compiling and
-optimizing the <code>.apk</code> file. If you are using Eclipse with the ADT plugin or you are using
-the Ant build script from the command line, you can automate the entire build process.</p>
+optimizing the <code>.apk</code> file. If you are using Android Studio or you are using
+the Gradle build system from the command line, you can automate the entire build process. See
+<a href="{@docRoot}tools/gradle/studio-build.html">Building Your Project with Gradle</a>.</p>
-<h3>Building with Eclipse</h3>
+<h3>Building with Android Studio</h3>
-<p>You can use the Eclipse Export Wizard to build a release-ready <code>.apk</code> file that is
-signed with your private key and optimized. To learn how to run the Export Wizard, see
-<a href="{@docRoot}tools/publishing/app-signing.html#ExportWizard">Compile and sign with Eclipse
-ADT</a>. The Export Wizard compiles your application for release, signs your application with your
-private key, and optimizes your application with the zipalign tool. The Export Wizard should run
-successfully if you have run or debugged your application from Eclipse and you have no errors in
-your application (see <a href="{@docRoot}tools/building/building-eclipse.html">Building
-and Running from Eclipse with ADT</a> for more information.</p>
+<p>You can use the Gradle build system, integrated with Android Studio to build a release-ready
+<code>.apk</code> file that is signed with your private key and optimized. To learn how to setup and
+run builds from Android Studio, see
+<a href="{@docRoot}tools/gradle/studio-build.html">Building Your Project with Gradle</a>.</p>
-<p>The Export Wizard assumes that you have a <a href="#billing-keys">certificate and private key</a>
+<p>The build process assumes that you have a certificate and private key
suitable for signing your application. If you do not have a suitable certificate and private key,
-the Export Wizard will help you generate one (see
-<a href="{@docRoot}tools/publishing/app-signing.html">Signing Your Applications</a> for more
-information about the signing process and signing guidelines.</p>
-
-<h3>Building with Ant</h3>
-
-<p>You can use the Ant build script (included in the Android SDK) to build a release-ready
-<code>.apk</code> file that is signed with your private key and optimized. To learn how to do this,
-see <a href="{@docRoot}tools/building/building-cmdline.html#ReleaseMode">Building in
-Release Mode</a>. This build method assumes you have a <a href="#billing-keys">certificate and
-private key</a> suitable for signing your application. If you do not have a suitable certificate and
-private key, the Export Wizard will help you generate one (see
-<a href="{@docRoot}tools/publishing/app-signing.html">Signing Your Applications</a> for more
-information about the signing process and signing guidelines.</p>
+Android Studio can help you generate one. For more information about the signing process, see
+<a href="{@docRoot}tools/publishing/app-signing.html">Signing Your Applications</a>.</p>
+
<h2 id="publishing-resources">Preparing External Servers and Resources</h2>
diff --git a/docs/html/tools/publishing/publishing_overview.jd b/docs/html/tools/publishing/publishing_overview.jd
index c4b3bdf..3c56402 100644
--- a/docs/html/tools/publishing/publishing_overview.jd
+++ b/docs/html/tools/publishing/publishing_overview.jd
@@ -67,11 +67,13 @@ tasks:</p>
<a href="{@docRoot}guide/topics/manifest/manifest-element.html">&lt;manifest&gt;</a>
element. You may also have to configure several other settings to meet Google Play
requirements or accomodate whatever method you're using to release your application.</p>
+ <p>If you are using Gradle build files, you can use the <em>release</em> build type to set
+ your build settings for the published version of your app. </p>
</li>
<li>Building and signing a release version of your application.
- <p>The Android Development Tools (ADT) plugin and the Ant build script that are provided
- with the Android SDK tools provide everything you need to build and sign a release version of
- your application.</p>
+ <p>You can use the Gradle build files with the <em>release</em> build type to build and sign a
+ release version of your application. See
+ <a href="{@docRoot}tools/gradle/studio-build.html">Building Your Project with Gradle</a>.</p>
</li>
<li>Testing the release version of your application.
<p>Before you distribute your application, you should thoroughly test the release version on at
diff --git a/docs/html/tools/publishing/versioning.jd b/docs/html/tools/publishing/versioning.jd
index 6d3ec2f..f56dbc3 100644
--- a/docs/html/tools/publishing/versioning.jd
+++ b/docs/html/tools/publishing/versioning.jd
@@ -95,7 +95,8 @@ value for display to users.</p>
</ul>
<p>You define both of these version attributes in the
-<code>&lt;manifest&gt;</code> element of the manifest file. </p>
+<code>&lt;manifest&gt;</code> element of the manifest file or the Gradle build file. See
+<a href="{@docRoot}tools/gradle/studio-build.html">Building Your Project with Gradle</a>.</p>
<p>Here's an example manifest that shows the <code>android:versionCode</code>
and <code>android:versionName</code> attributes in the
@@ -171,4 +172,6 @@ maximum API Level. </p>
<p>For more information, see the <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>&lt;uses-sdk&gt;</code></a>
manifest element documentation and the <a
-href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API Levels</a> document.</p>
+href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API Levels</a> document. For
+Gradle build settings, see
+<a href="{@docRoot}tools/gradle/studio-build.html">Building Your Project with Gradle</a>.</p>
diff --git a/docs/html/tools/tools_toc.cs b/docs/html/tools/tools_toc.cs
index 022afae..f89c9c9 100644
--- a/docs/html/tools/tools_toc.cs
+++ b/docs/html/tools/tools_toc.cs
@@ -212,7 +212,7 @@
<div class="nav-section-header">
<a href="<?cs var:toroot ?>tools/gradle/index.html">Build System</a>
</div>
- <ul>
+ <ul>
<li><a href="<?cs var:toroot ?>tools/gradle/studio-build.html">
Building Your Project with Gradle</a></li>
<li><a href="<?cs var:toroot ?>tools/gradle/gradle-ref.html">
@@ -292,12 +292,12 @@ class="en">Platforms</span></a></li>
<span class="en">Eclipse with ADT</span></a>
</div>
<ul>
- <li><a href="<?cs var:toroot ?>sdk/installing/migrate.html">Migrating to Android Studio</a></li>
- <li><a href="<?cs var:toroot ?>sdk/installing/installing-adt.html">Installing</a></li>
- <li><a href="<?cs var:toroot ?>tools/projects/projects-eclipse.html">Setting Up Projects</a></li>
- <li><a href="<?cs var:toroot ?>tools/building/building-eclipse.html">Building</a></li>
- <li><a href="<?cs var:toroot ?>tools/debugging/debugging-projects.html">Debugging</a></li>
+ <li><a href="<?cs var:toroot ?>tools/eclipse/migrate-adt.html">Migrating to Android Studio</a></li>
+ <li><a href="<?cs var:toroot ?>tools/projects/projects-eclipse.html">Managing Projects</a></li>
+ <li><a href="<?cs var:toroot ?>tools/building/building-eclipse.html">Building and Running</a></li>
<li><a href="<?cs var:toroot ?>tools/testing/testing_eclipse.html">Testing</a></li>
+ <li><a href="<?cs var:toroot ?>tools/debugging/debugging-projects.html">Debugging</a></li>
+ <li><a href="<?cs var:toroot ?>tools/publishing/app-signing-eclipse.html">Signing Your Apps</a></li>
</ul>
</li><!-- end of Eclipse -->
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 68fd296..2748030 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -54,19 +54,20 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
private DrawableContainerState mDrawableContainerState;
private Rect mHotspotBounds;
private Drawable mCurrDrawable;
+ private Drawable mLastDrawable;
private int mAlpha = 0xFF;
/** Whether setAlpha() has been called at least once. */
private boolean mHasAlpha;
private int mCurIndex = -1;
+ private int mLastIndex = -1;
private boolean mMutated;
// Animations.
private Runnable mAnimationRunnable;
private long mEnterAnimationEnd;
private long mExitAnimationEnd;
- private Drawable mLastDrawable;
// overrides from Drawable
@@ -255,6 +256,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
if (mLastDrawable != null) {
mLastDrawable.jumpToCurrentState();
mLastDrawable = null;
+ mLastIndex = -1;
changed = true;
}
if (mCurrDrawable != null) {
@@ -426,9 +428,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
}
if (mCurrDrawable != null) {
mLastDrawable = mCurrDrawable;
+ mLastIndex = mCurIndex;
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
+ mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
@@ -522,6 +526,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
if (mExitAnimationEnd <= now) {
mLastDrawable.setVisible(false, false);
mLastDrawable = null;
+ mLastIndex = -1;
mExitAnimationEnd = 0;
} else {
int animAlpha = (int)((mExitAnimationEnd-now)*255)
@@ -1103,5 +1108,13 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
protected void setConstantState(DrawableContainerState state) {
mDrawableContainerState = state;
+
+ // The locally cached drawables may have changed.
+ if (mCurIndex >= 0) {
+ mCurrDrawable = state.getChild(mCurIndex);
+ }
+ if (mLastIndex >= 0) {
+ mLastDrawable = state.getChild(mLastIndex);
+ }
}
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 8cbc239..d5d5d51 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -832,6 +832,10 @@ public class RippleDrawable extends LayerDrawable {
// LayerDrawable creates a new state using createConstantState, so
// this should always be a safe cast.
mState = (RippleState) mLayerState;
+
+ // The locally cached drawable may have changed.
+ mMask = findDrawableByLayerId(R.id.mask);
+
return this;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7e2e657..bde19d9 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -30,6 +30,7 @@ import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import android.Manifest;
import android.app.AppOpsManager;
@@ -3907,7 +3908,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ endIndex + " " + cur);
if (cur == top) {
// Verify start of the chain.
- if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != -1) {
+ if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
Slog.wtf(TAG, "Bad chain @" + endIndex
+ ": first task has next affiliate: " + prev);
sane = false;
@@ -3926,7 +3927,7 @@ public final class ActivityManagerService extends ActivityManagerNative
break;
}
}
- if (cur.mPrevAffiliateTaskId == -1) {
+ if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
// Chain ends here.
if (cur.mPrevAffiliate != null) {
Slog.wtf(TAG, "Bad chain @" + endIndex
@@ -3991,7 +3992,8 @@ public final class ActivityManagerService extends ActivityManagerNative
final void addRecentTaskLocked(TaskRecord task) {
final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
- || task.mNextAffiliateTaskId != -1 || task.mPrevAffiliateTaskId != -1;
+ || task.mNextAffiliateTaskId != INVALID_TASK_ID
+ || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
int N = mRecentTasks.size();
// Quick case: check if the top-most recent task is the same.
@@ -6222,6 +6224,17 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public void systemBackupRestored() {
+ synchronized (this) {
+ if (mSystemReady) {
+ mTaskPersister.restoreTasksFromOtherDeviceLocked();
+ } else {
+ Slog.w(TAG, "System backup restored before system is ready");
+ }
+ }
+ }
+
final void ensureBootCompleted() {
boolean booting;
boolean enableScreen;
@@ -8036,7 +8049,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Compose the recent task info
ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
- rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
+ rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
rti.persistentId = tr.taskId;
rti.baseIntent = new Intent(tr.getBaseIntent());
rti.origActivity = tr.origActivity;
@@ -8259,7 +8272,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (trimIdx >= 0) {
// If this would have caused a trim, then we'll abort because that
// means it would be added at the end of the list but then just removed.
- return -1;
+ return INVALID_TASK_ID;
}
final int N = mRecentTasks.size();
@@ -11147,6 +11160,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mRecentTasks == null) {
mRecentTasks = mTaskPersister.restoreTasksLocked();
+ mTaskPersister.restoreTasksFromOtherDeviceLocked();
if (!mRecentTasks.isEmpty()) {
mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks);
}
@@ -15793,6 +15807,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
} else {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
+ if (userId == UserHandle.USER_OWNER) {
+ mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
+ }
}
}
break;
@@ -15810,6 +15827,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (replacing) {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
}
+ if (userId == UserHandle.USER_OWNER) {
+ mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
+ }
}
break;
case Intent.ACTION_TIMEZONE_CHANGED:
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e37d5f3..912ca62 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,6 +16,10 @@
package com.android.server.am;
+import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
+import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
import android.app.ActivityManager.TaskDescription;
import android.os.PersistableBundle;
import android.os.Trace;
@@ -1052,12 +1056,12 @@ final class ActivityRecord {
static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
final ActivityRecord r = ActivityRecord.forToken(token);
if (r == null) {
- return -1;
+ return INVALID_TASK_ID;
}
final TaskRecord task = r.task;
final int activityNdx = task.mActivities.indexOf(r);
if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
- return -1;
+ return INVALID_TASK_ID;
}
return task.taskId;
}
@@ -1139,7 +1143,7 @@ final class ActivityRecord {
}
}
- static ActivityRecord restoreFromXml(XmlPullParser in, int taskId,
+ static ActivityRecord restoreFromXml(XmlPullParser in,
ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException {
Intent intent = null;
PersistableBundle persistentState = null;
@@ -1155,8 +1159,8 @@ final class ActivityRecord {
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
final String attrValue = in.getAttributeValue(attrNdx);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "ActivityRecord: attribute name=" +
- attrName + " value=" + attrValue);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
+ "ActivityRecord: attribute name=" + attrName + " value=" + attrValue);
if (ATTR_ID.equals(attrName)) {
createTime = Long.valueOf(attrValue);
} else if (ATTR_LAUNCHEDFROMUID.equals(attrName)) {
@@ -1181,15 +1185,15 @@ final class ActivityRecord {
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
final String name = in.getName();
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
- "ActivityRecord: START_TAG name=" + name);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "ActivityRecord: START_TAG name=" + name);
if (TAG_INTENT.equals(name)) {
intent = Intent.restoreFromXml(in);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
- "ActivityRecord: intent=" + intent);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "ActivityRecord: intent=" + intent);
} else if (TAG_PERSISTABLEBUNDLE.equals(name)) {
persistentState = PersistableBundle.restoreFromXml(in);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
+ if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
"ActivityRecord: persistentState=" + persistentState);
} else {
Slog.w(TAG, "restoreActivity: unexpected name=" + name);
@@ -1232,7 +1236,7 @@ final class ActivityRecord {
@Override
public String toString() {
if (stringName != null) {
- return stringName + " t" + (task == null ? -1 : task.taskId) +
+ return stringName + " t" + (task == null ? INVALID_TASK_ID : task.taskId) +
(finishing ? " f}" : "}");
}
StringBuilder sb = new StringBuilder(128);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d9396d8..262b4f1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1314,7 +1314,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
}
- ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
final int launchFlags = intent.getFlags();
@@ -1391,6 +1390,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
+ final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
+
if (err != ActivityManager.START_SUCCESS) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 9311f25..629a05d 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,16 +16,27 @@
package com.android.server.am;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
+
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -38,11 +49,18 @@ import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
+
+import libcore.io.IoUtils;
+
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
public class TaskPersister {
static final String TAG = "TaskPersister";
- static final boolean DEBUG = false;
+ static final boolean DEBUG_PERSISTER = false;
+ static final boolean DEBUG_RESTORER = false;
/** When not flushing don't write out files faster than this */
private static final long INTER_WRITE_DELAY_MS = 500;
@@ -67,12 +85,17 @@ public class TaskPersister {
// contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the
// ancestral device's dataset. This needs to match the RECENTS_TASK_RESTORE_DIR
// value in RecentsBackupHelper.
- private static final String RESTORED_TASKS = "restored_" + TASKS_DIRNAME;
+ private static final String RESTORED_TASKS_DIRNAME = "restored_" + TASKS_DIRNAME;
+
+ // Max time to wait for the application/package of a restored task to be installed
+ // before giving up.
+ private static final long MAX_INSTALL_WAIT_TIME = DateUtils.DAY_IN_MILLIS;
private static final String TAG_TASK = "task";
static File sImagesDir;
static File sTasksDir;
+ static File sRestoredTasksDir;
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
@@ -105,10 +128,20 @@ public class TaskPersister {
ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
+ // Map of tasks that were backed-up on a different device that can be restored on this device.
+ // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
+ private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
+ new ArrayMap<>(10);
+
+ // The next time in milliseconds we will remove expired task from
+ // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
+ // tasks.
+ private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
sTasksDir = new File(systemDir, TASKS_DIRNAME);
if (!sTasksDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
if (!sTasksDir.mkdir()) {
Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
}
@@ -116,12 +149,14 @@ public class TaskPersister {
sImagesDir = new File(systemDir, IMAGES_DIRNAME);
if (!sImagesDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
if (!sImagesDir.mkdir()) {
Slog.e(TAG, "Failure creating images directory " + sImagesDir);
}
}
+ sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
+
mStackSupervisor = stackSupervisor;
mService = stackSupervisor.mService;
@@ -138,8 +173,8 @@ public class TaskPersister {
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof ImageWriteQueueItem &&
((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
- if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
- " from write queue");
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
+ + ((ImageWriteQueueItem) item).mFilename + " from write queue");
mWriteQueue.remove(queueNdx);
}
}
@@ -184,9 +219,9 @@ public class TaskPersister {
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
- + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
- + " Callers=" + Debug.getCallers(4));
+ if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
+ + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+ + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
notifyAll();
}
@@ -228,7 +263,7 @@ public class TaskPersister {
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
SystemClock.uptimeMillis() + " mNextWriteTime=" +
mNextWriteTime + " Callers=" + Debug.getCallers(4));
notifyAll();
@@ -262,12 +297,12 @@ public class TaskPersister {
}
private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
- if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
final XmlSerializer xmlSerializer = new FastXmlSerializer();
StringWriter stringWriter = new StringWriter();
xmlSerializer.setOutput(stringWriter);
- if (DEBUG) xmlSerializer.setFeature(
+ if (DEBUG_PERSISTER) xmlSerializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
// save task
@@ -326,7 +361,7 @@ public class TaskPersister {
for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
File taskFile = recentFiles[taskNdx];
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
BufferedReader reader = null;
boolean deleteFile = false;
try {
@@ -339,11 +374,12 @@ public class TaskPersister {
event != XmlPullParser.END_TAG) {
final String name = in.getName();
if (event == XmlPullParser.START_TAG) {
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
if (TAG_TASK.equals(name)) {
final TaskRecord task =
TaskRecord.restoreFromXml(in, mStackSupervisor);
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
task);
if (task != null) {
task.isPersistable = true;
@@ -371,20 +407,16 @@ public class TaskPersister {
Slog.e(TAG, "Failing file: " + fileToString(taskFile));
deleteFile = true;
} finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- }
- }
- if (!DEBUG && deleteFile) {
- if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
+ IoUtils.closeQuietly(reader);
+ if (!DEBUG_PERSISTER && deleteFile) {
+ if (true || DEBUG_PERSISTER)
+ Slog.d(TAG, "Deleting file=" + taskFile.getName());
taskFile.delete();
}
}
}
- if (!DEBUG) {
+ if (!DEBUG_PERSISTER) {
removeObsoleteFiles(recoveredTaskIds);
}
@@ -415,8 +447,8 @@ public class TaskPersister {
}
private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
- " files=" + files);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
+ + persistentTaskIds + " files=" + files);
if (files == null) {
Slog.e(TAG, "File error accessing recents directory (too many files open?).");
return;
@@ -429,14 +461,14 @@ public class TaskPersister {
final int taskId;
try {
taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
} catch (Exception e) {
Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
file.delete();
continue;
}
if (!persistentTaskIds.contains(taskId)) {
- if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
+ if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
file.getName());
file.delete();
}
@@ -450,10 +482,363 @@ public class TaskPersister {
}
static Bitmap restoreImage(String filename) {
- if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
}
+ /**
+ * Tries to restore task that were backed-up on a different device onto this device.
+ */
+ void restoreTasksFromOtherDeviceLocked() {
+ readOtherDeviceTasksFromDisk();
+ addOtherDeviceTasksToRecentsLocked();
+ }
+
+ /**
+ * Read the tasks that were backed-up on a different device and can be restored to this device
+ * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
+ * time to clear out other device tasks that have not been restored on this device
+ * within the allotted time.
+ */
+ private void readOtherDeviceTasksFromDisk() {
+ synchronized (mOtherDeviceTasksMap) {
+ // Clear out current map and expiration time.
+ mOtherDeviceTasksMap.clear();
+ mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+ final File[] taskFiles;
+ if (!sRestoredTasksDir.exists()
+ || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
+ // Nothing to do if there are no tasks to restore.
+ return;
+ }
+
+ long earliestMtime = System.currentTimeMillis();
+ SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
+ new SparseArray<>(taskFiles.length);
+
+ // Read new tasks from disk
+ for (int i = 0; i < taskFiles.length; ++i) {
+ final File taskFile = taskFiles[i];
+ if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
+ + taskFile.getName());
+
+ final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
+
+ if (task == null) {
+ // Go ahead and remove the file on disk if we are unable to create a task from
+ // it.
+ if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
+ + taskFile.getName() + "...deleting file.");
+ taskFile.delete();
+ continue;
+ }
+
+ List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
+ if (tasks == null) {
+ tasks = new ArrayList<>();
+ tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
+ }
+ tasks.add(task);
+ final long taskMtime = taskFile.lastModified();
+ if (earliestMtime > taskMtime) {
+ earliestMtime = taskMtime;
+ }
+ }
+
+ if (tasksByAffiliateIds.size() > 0) {
+ // Sort each affiliated tasks chain by taskId which is the order they were created
+ // that should always be correct...Then add to task map.
+ for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
+ List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
+ Collections.sort(chain);
+ // Package name of the root task in the affiliate chain.
+ final String packageName =
+ chain.get(chain.size()-1).mComponentName.getPackageName();
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+ if (chains == null) {
+ chains = new ArrayList<>();
+ mOtherDeviceTasksMap.put(packageName, chains);
+ }
+ chains.add(chain);
+ }
+
+ // Set expiration time.
+ mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
+ + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+ }
+ }
+ }
+
+ /**
+ * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
+ * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
+ */
+ private void removeExpiredTasksIfNeeded() {
+ synchronized (mOtherDeviceTasksMap) {
+ final long now = System.currentTimeMillis();
+ if (mOtherDeviceTasksMap.isEmpty() || now < mExpiredTasksCleanupTime) {
+ return;
+ }
+
+ long earliestNonExpiredMtime = now;
+ mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+ // Remove expired backed-up tasks that have not been restored. We only want to
+ // remove task if it is safe to remove all tasks in the affiliation chain.
+ for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
+
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
+ for (int j = chains.size() - 1; j >= 0 ; j--) {
+
+ List<OtherDeviceTask> chain = chains.get(j);
+ boolean removeChain = true;
+ for (int k = chain.size() - 1; k >= 0 ; k--) {
+ OtherDeviceTask task = chain.get(k);
+ final long taskLastModified = task.mFile.lastModified();
+ if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
+ // File has not expired yet...but we keep looping to get the earliest
+ // mtime.
+ if (earliestNonExpiredMtime > taskLastModified) {
+ earliestNonExpiredMtime = taskLastModified;
+ }
+ removeChain = false;
+ }
+ }
+ if (removeChain) {
+ for (int k = chain.size() - 1; k >= 0; k--) {
+ final File file = chain.get(k).mFile;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
+ + file.getName() + " mapped to not installed component="
+ + chain.get(k).mComponentName);
+ file.delete();
+ }
+ chains.remove(j);
+ }
+ }
+ if (chains.isEmpty()) {
+ final String packageName = mOtherDeviceTasksMap.keyAt(i);
+ mOtherDeviceTasksMap.removeAt(i);
+ if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
+ + " from task map");
+ }
+ }
+
+ // Reset expiration time if there is any task remaining.
+ if (!mOtherDeviceTasksMap.isEmpty()) {
+ mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
+ + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+ }
+ }
+ }
+
+ /**
+ * Tries to add all backed-up tasks from another device to this device recent's list.
+ */
+ private void addOtherDeviceTasksToRecentsLocked() {
+ synchronized (mOtherDeviceTasksMap) {
+ for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
+ addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Tries to add backed-up tasks that are associated with the input package from
+ * another device to this device recent's list.
+ */
+ void addOtherDeviceTasksToRecentsLocked(String packageName) {
+ synchronized (mOtherDeviceTasksMap) {
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+ if (chains == null) {
+ return;
+ }
+
+ for (int i = chains.size() - 1; i >= 0; i--) {
+ List<OtherDeviceTask> chain = chains.get(i);
+ if (!canAddOtherDeviceTaskChain(chain)) {
+ if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
+ + " for package=" + packageName);
+ continue;
+ }
+
+ // Generate task records for this chain.
+ List<TaskRecord> tasks = new ArrayList<>();
+ TaskRecord prev = null;
+ for (int j = chain.size() - 1; j >= 0; j--) {
+ TaskRecord task = createTaskRecordLocked(chain.get(j));
+ if (task == null) {
+ // There was a problem in creating one of this task records in this chain.
+ // There is no way we can continue...
+ if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
+ + chain.get(j).mFile + " for package=" + packageName);
+ break;
+ }
+
+ // Wire-up affiliation chain.
+ if (prev == null) {
+ task.mPrevAffiliate = null;
+ task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+ task.mAffiliatedTaskId = task.taskId;
+ } else {
+ prev.mNextAffiliate = task;
+ prev.mNextAffiliateTaskId = task.taskId;
+ task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
+ task.mPrevAffiliate = prev;
+ task.mPrevAffiliateTaskId = prev.taskId;
+ }
+ prev = task;
+ tasks.add(0, task);
+ }
+
+ // Add tasks to recent's if we were able to create task records for all the tasks
+ // in the chain.
+ if (tasks.size() == chain.size()) {
+ // Make sure there is space in recent's to add the new task. If there is space
+ // to the to the back.
+ // TODO: Would be more fancy to interleave the new tasks into recent's based on
+ // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
+ // adding to the back of the list.
+ int spaceLeft =
+ ActivityManager.getMaxRecentTasksStatic()
+ - mService.mRecentTasks.size();
+ if (spaceLeft >= tasks.size()) {
+ mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
+ for (int k = tasks.size() - 1; k >= 0; k--) {
+ // Persist new tasks.
+ wakeup(tasks.get(k), false);
+ }
+
+ if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
+ + " tasks to recent's for" + " package=" + packageName);
+ } else {
+ if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
+ + tasks.size() + ") != chain.size(" + chain.size()
+ + ") for package=" + packageName);
+ }
+ } else {
+ if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
+ + tasks.size() + " tasks for package=" + packageName);
+ }
+
+ // Clean-up structures
+ for (int j = chain.size() - 1; j >= 0; j--) {
+ chain.get(j).mFile.delete();
+ }
+ chains.remove(i);
+ if (chains.isEmpty()) {
+ // The fate of all backed-up tasks associated with this package has been
+ // determine. Go ahead and remove it from the to-process list.
+ mOtherDeviceTasksMap.remove(packageName);
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "Removed package=" + packageName + " from restore map");
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates and returns {@link TaskRecord} for the task from another device that can be used on
+ * this device. Returns null if the operation failed.
+ */
+ private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
+ File file = other.mFile;
+ BufferedReader reader = null;
+ TaskRecord task = null;
+ if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
+
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+ && event != XmlPullParser.END_TAG) {
+ final String name = in.getName();
+ if (event == XmlPullParser.START_TAG) {
+
+ if (TAG_TASK.equals(name)) {
+ // Create a task record using a task id that is valid for this device.
+ task = TaskRecord.restoreFromXml(
+ in, mStackSupervisor, mStackSupervisor.getNextTaskId());
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
+
+ if (task != null) {
+ task.isPersistable = true;
+ task.inRecents = true;
+ // Task can/should only be backed-up/restored for device owner.
+ task.userId = UserHandle.USER_OWNER;
+ // Clear out affiliated ids that are no longer valid on this device.
+ task.mAffiliatedTaskId = INVALID_TASK_ID;
+ task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+ task.mNextAffiliateTaskId = INVALID_TASK_ID;
+ } else {
+ Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
+ + fileToString(file));
+ }
+ } else {
+ Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
+ + " name=" + name);
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+ Slog.e(TAG, "Failing file: " + fileToString(file));
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ return task;
+ }
+
+ /**
+ * Returns true if the input task chain backed-up from another device can be restored on this
+ * device.
+ */
+ private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
+
+ // Get component names of all the tasks in the chain.
+ // Mainly doing this to reduce checking for a component twice if two or more
+ // affiliations belong to the same component which is highly likely.
+ ArraySet<ComponentName> componentsToCheck = new ArraySet<>();
+ for (int i = 0; i < chain.size(); i++) {
+
+ OtherDeviceTask task = chain.get(i);
+ // Quick check, we can't add the task chain if any of its task files don't exist.
+ if (!task.mFile.exists()) {
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "Can't add chain due to missing file=" + task.mFile);
+ return false;
+ }
+ componentsToCheck.add(task.mComponentName);
+ }
+
+ boolean canAdd = true;
+ try {
+ // Check to see if all the components for this task chain are installed.
+ final IPackageManager pm = AppGlobals.getPackageManager();
+ for (int i = 0; canAdd && i < componentsToCheck.size(); i++) {
+ ComponentName cn = componentsToCheck.valueAt(i);
+ canAdd &= pm.getActivityInfo(cn, 0, UserHandle.USER_OWNER) != null;
+ if (DEBUG_RESTORER) Slog.d(TAG, "ComponentName=" + cn + " installed=" + canAdd);
+ }
+ } catch (RemoteException e) {
+ // Should not happen???
+ canAdd = false;
+ }
+
+ if (DEBUG_RESTORER) Slog.d(TAG, "canAdd=" + canAdd);
+ return canAdd;
+ }
+
private class LazyTaskWriterThread extends Thread {
LazyTaskWriterThread(String name) {
@@ -472,21 +857,22 @@ public class TaskPersister {
probablyDone = mWriteQueue.isEmpty();
}
if (probablyDone) {
- if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
persistentTaskIds.clear();
synchronized (mService) {
final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
- if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = tasks.get(taskNdx);
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
" persistable=" + task.isPersistable);
if ((task.isPersistable || task.inRecents)
- && !task.stack.isHomeStack()) {
- if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+ && (task.stack == null || !task.stack.isHomeStack())) {
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "adding to persistentTaskIds task=" + task);
persistentTaskIds.add(task.taskId);
} else {
- if (DEBUG) Slog.d(TAG,
+ if (DEBUG_PERSISTER) Slog.d(TAG,
"omitting from persistentTaskIds task=" + task);
}
}
@@ -500,7 +886,7 @@ public class TaskPersister {
if (mNextWriteTime != FLUSH_QUEUE) {
// The next write we don't have to wait so long.
mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
- if (DEBUG) Slog.d(TAG, "Next write time may be in " +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
}
@@ -510,8 +896,13 @@ public class TaskPersister {
mNextWriteTime = 0; // idle.
TaskPersister.this.notifyAll(); // wake up flush() if needed.
}
+
+ // See if we need to remove any expired back-up tasks before waiting.
+ removeExpiredTasksIfNeeded();
+
try {
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
TaskPersister.this.wait();
} catch (InterruptedException e) {
}
@@ -521,11 +912,12 @@ public class TaskPersister {
item = mWriteQueue.remove(0);
long now = SystemClock.uptimeMillis();
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
- mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
+ + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+ + mWriteQueue.size());
while (now < mNextWriteTime) {
try {
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
(mNextWriteTime - now));
TaskPersister.this.wait(mNextWriteTime - now);
} catch (InterruptedException e) {
@@ -540,7 +932,7 @@ public class TaskPersister {
ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
final String filename = imageWriteQueueItem.mFilename;
final Bitmap bitmap = imageWriteQueueItem.mImage;
- if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
FileOutputStream imageFile = null;
try {
imageFile = new FileOutputStream(new File(sImagesDir, filename));
@@ -548,23 +940,18 @@ public class TaskPersister {
} catch (Exception e) {
Slog.e(TAG, "saveImage: unable to save " + filename, e);
} finally {
- if (imageFile != null) {
- try {
- imageFile.close();
- } catch (IOException e) {
- }
- }
+ IoUtils.closeQuietly(imageFile);
}
} else if (item instanceof TaskWriteQueueItem) {
// Write out one task.
StringWriter stringWriter = null;
TaskRecord task = ((TaskWriteQueueItem) item).mTask;
- if (DEBUG) Slog.d(TAG, "Writing task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
synchronized (mService) {
if (task.inRecents) {
// Still there.
try {
- if (DEBUG) Slog.d(TAG, "Saving task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
stringWriter = saveToXml(task);
} catch (IOException e) {
} catch (XmlPullParserException e) {
@@ -594,4 +981,100 @@ public class TaskPersister {
}
}
}
+
+ /**
+ * Helper class for holding essential information about task that were backed-up on a different
+ * device that can be restored on this device.
+ */
+ private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
+ final File mFile;
+ // See {@link TaskRecord} for information on the fields below.
+ final ComponentName mComponentName;
+ final int mTaskId;
+ final int mAffiliatedTaskId;
+
+ private OtherDeviceTask(
+ File file, ComponentName componentName, int taskId, int affiliatedTaskId) {
+ mFile = file;
+ mComponentName = componentName;
+ mTaskId = taskId;
+ mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
+ }
+
+ @Override
+ public int compareTo(OtherDeviceTask another) {
+ return mTaskId - another.mTaskId;
+ }
+
+ /**
+ * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
+ *
+ * @param file input file that contains the complete task information.
+ * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
+ */
+ static OtherDeviceTask createFromFile(File file) {
+ if (file == null || !file.exists()) {
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
+ return null;
+ }
+
+ BufferedReader reader = null;
+
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ event != XmlPullParser.START_TAG) {
+ // Skip to the start tag or end of document
+ }
+
+ if (event == XmlPullParser.START_TAG) {
+ final String name = in.getName();
+
+ if (TAG_TASK.equals(name)) {
+ ComponentName componentName = null;
+ int taskId = INVALID_TASK_ID;
+ int taskAffiliation = INVALID_TASK_ID;
+ for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
+ final String attrName = in.getAttributeName(j);
+ final String attrValue = in.getAttributeValue(j);
+ if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
+ componentName = ComponentName.unflattenFromString(attrValue);
+ } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
+ taskId = Integer.valueOf(attrValue);
+ } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
+ taskAffiliation = Integer.valueOf(attrValue);
+ }
+ }
+ if (componentName == null || taskId == INVALID_TASK_ID) {
+ if (DEBUG_RESTORER) Slog.e(TAG,
+ "createFromFile: FAILED componentName=" + componentName
+ + " taskId=" + taskId + " file=" + file);
+ return null;
+ }
+ if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
+ + file.getName() + " componentName=" + componentName
+ + " taskId=" + taskId);
+ return new OtherDeviceTask(file, componentName, taskId, taskAffiliation);
+ } else {
+ Slog.wtf(TAG,
+ "createFromFile: Unknown xml event=" + event + " name=" + name);
+ }
+ } else {
+ Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ // Something went wrong...
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d726685..c3eda71 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -21,6 +21,8 @@ import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
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.TaskPersister.DEBUG_PERSISTER;
+import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
import android.app.Activity;
import android.app.ActivityManager;
@@ -52,10 +54,10 @@ import java.io.PrintWriter;
import java.util.ArrayList;
final class TaskRecord {
- private static final String ATTR_TASKID = "task_id";
+ static final String ATTR_TASKID = "task_id";
private static final String TAG_INTENT = "intent";
private static final String TAG_AFFINITYINTENT = "affinity_intent";
- private static final String ATTR_REALACTIVITY = "real_activity";
+ static final String ATTR_REALACTIVITY = "real_activity";
private static final String ATTR_ORIGACTIVITY = "orig_activity";
private static final String TAG_ACTIVITY = "activity";
private static final String ATTR_AFFINITY = "affinity";
@@ -71,7 +73,7 @@ final class TaskRecord {
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
- private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
+ static final String ATTR_TASK_AFFILIATION = "task_affiliation";
private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
@@ -82,6 +84,8 @@ final class TaskRecord {
static final boolean IGNORE_RETURN_TO_RECENTS = true;
+ static final int INVALID_TASK_ID = -1;
+
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
String rootAffinity; // Initial base affinity, or null; does not change from initial root.
@@ -151,9 +155,9 @@ final class TaskRecord {
int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
int mAffiliatedTaskColor; // color of the parent task affiliation.
TaskRecord mPrevAffiliate; // previous task in affiliated chain.
- int mPrevAffiliateTaskId = -1; // previous id for persistence.
+ int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
TaskRecord mNextAffiliate; // next task in affiliated chain.
- int mNextAffiliateTaskId = -1; // next id for persistence.
+ int mNextAffiliateTaskId = INVALID_TASK_ID; // next id for persistence.
// For relaunching the task from recents as though it was launched by the original launcher.
int mCallingUid;
@@ -363,12 +367,12 @@ final class TaskRecord {
void setPrevAffiliate(TaskRecord prevAffiliate) {
mPrevAffiliate = prevAffiliate;
- mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
+ mPrevAffiliateTaskId = prevAffiliate == null ? INVALID_TASK_ID : prevAffiliate.taskId;
}
void setNextAffiliate(TaskRecord nextAffiliate) {
mNextAffiliate = nextAffiliate;
- mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
+ mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
}
// Close up recents linked list.
@@ -875,6 +879,10 @@ final class TaskRecord {
static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
throws IOException, XmlPullParserException {
+ return restoreFromXml(in, stackSupervisor, INVALID_TASK_ID);
+ }
+ static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor,
+ int inTaskId) throws IOException, XmlPullParserException {
Intent intent = null;
Intent affinityIntent = null;
ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
@@ -894,23 +902,23 @@ final class TaskRecord {
long lastActiveTime = -1;
long lastTimeOnTop = 0;
boolean neverRelinquishIdentity = true;
- int taskId = -1;
+ int taskId = inTaskId;
final int outerDepth = in.getDepth();
TaskDescription taskDescription = new TaskDescription();
- int taskAffiliation = -1;
+ int taskAffiliation = INVALID_TASK_ID;
int taskAffiliationColor = 0;
- int prevTaskId = -1;
- int nextTaskId = -1;
+ int prevTaskId = INVALID_TASK_ID;
+ int nextTaskId = INVALID_TASK_ID;
int callingUid = -1;
String callingPackage = "";
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
final String attrValue = in.getAttributeValue(attrNdx);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
- attrName + " value=" + attrValue);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
+ "TaskRecord: attribute name=" + attrName + " value=" + attrValue);
if (ATTR_TASKID.equals(attrName)) {
- taskId = Integer.valueOf(attrValue);
+ if (taskId == INVALID_TASK_ID) taskId = Integer.valueOf(attrValue);
} else if (ATTR_REALACTIVITY.equals(attrName)) {
realActivity = ComponentName.unflattenFromString(attrValue);
} else if (ATTR_ORIGACTIVITY.equals(attrName)) {
@@ -966,17 +974,16 @@ final class TaskRecord {
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
final String name = in.getName();
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
- name);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" + name);
if (TAG_AFFINITYINTENT.equals(name)) {
affinityIntent = Intent.restoreFromXml(in);
} else if (TAG_INTENT.equals(name)) {
intent = Intent.restoreFromXml(in);
} else if (TAG_ACTIVITY.equals(name)) {
- ActivityRecord activity =
- ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
- activity);
+ ActivityRecord activity = ActivityRecord.restoreFromXml(in, stackSupervisor);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "TaskRecord: activity=" + activity);
if (activity != null) {
activities.add(activity);
}
@@ -1082,8 +1089,9 @@ final class TaskRecord {
pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity);
pw.print(" mReuseTask="); pw.println(mReuseTask);
}
- if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != -1 || mPrevAffiliate != null
- || mNextAffiliateTaskId != -1 || mNextAffiliate != null) {
+ if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != INVALID_TASK_ID
+ || mPrevAffiliate != null || mNextAffiliateTaskId != INVALID_TASK_ID
+ || mNextAffiliate != null) {
pw.print(prefix); pw.print("affiliation="); pw.print(mAffiliatedTaskId);
pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
pw.print(" (");