App ops: track system windows, monitoring changes.

Change-Id: I273e82bdad66ada3bf0f7ec9176bc304b9ee1ee8
diff --git a/Android.mk b/Android.mk
index dd155be..af3b37e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -166,6 +166,7 @@
 	core/java/android/speech/IRecognitionService.aidl \
 	core/java/android/speech/tts/ITextToSpeechCallback.aidl \
 	core/java/android/speech/tts/ITextToSpeechService.aidl \
+	core/java/com/android/internal/app/IAppOpsCallback.aidl \
 	core/java/com/android/internal/app/IAppOpsService.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
 	core/java/com/android/internal/app/IUsageStats.aidl \
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b92b9ce..241a9ae 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,10 +16,11 @@
 
 package android.app;
 
-import android.Manifest;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAppOpsCallback;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 import android.content.Context;
@@ -32,6 +33,8 @@
 public class AppOpsManager {
     final Context mContext;
     final IAppOpsService mService;
+    final HashMap<Callback, IAppOpsCallback> mModeWatchers
+            = new HashMap<Callback, IAppOpsCallback>();
 
     public static final int MODE_ALLOWED = 0;
     public static final int MODE_IGNORED = 1;
@@ -62,8 +65,9 @@
     public static final int OP_READ_ICC_SMS = 21;
     public static final int OP_WRITE_ICC_SMS = 22;
     public static final int OP_WRITE_SETTINGS = 23;
+    public static final int OP_SYSTEM_ALERT_WINDOW = 24;
     /** @hide */
-    public static final int _NUM_OP = 24;
+    public static final int _NUM_OP = 25;
 
     /**
      * This maps each operation to the operation that serves as the
@@ -98,6 +102,7 @@
             OP_READ_SMS,
             OP_WRITE_SMS,
             OP_WRITE_SETTINGS,
+            OP_SYSTEM_ALERT_WINDOW,
     };
 
     /**
@@ -129,6 +134,7 @@
             "READ_ICC_SMS",
             "WRITE_ICC_SMS",
             "WRITE_SETTINGS",
+            "SYSTEM_ALERT_WINDOW",
     };
 
     /**
@@ -160,6 +166,7 @@
             android.Manifest.permission.READ_SMS,
             android.Manifest.permission.WRITE_SMS,
             android.Manifest.permission.WRITE_SETTINGS,
+            android.Manifest.permission.SYSTEM_ALERT_WINDOW,
     };
 
     public static int opToSwitch(int op) {
@@ -167,6 +174,7 @@
     }
 
     public static String opToName(int op) {
+        if (op == OP_NONE) return "NONE";
         return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
     }
 
@@ -305,6 +313,10 @@
         };
     }
 
+    public interface Callback {
+        public void opChanged(int op, String packageName);
+    }
+
     public AppOpsManager(Context context, IAppOpsService service) {
         mContext = context;
         mService = service;
@@ -333,6 +345,36 @@
         }
     }
 
+    public void startWatchingMode(int op, String packageName, final Callback callback) {
+        synchronized (mModeWatchers) {
+            IAppOpsCallback cb = mModeWatchers.get(callback);
+            if (cb == null) {
+                cb = new IAppOpsCallback.Stub() {
+                    public void opChanged(int op, String packageName) {
+                        callback.opChanged(op, packageName);
+                    }
+                };
+                mModeWatchers.put(callback, cb);
+            }
+            try {
+                mService.startWatchingMode(op, packageName, cb);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void stopWatchingMode(Callback callback) {
+        synchronized (mModeWatchers) {
+            IAppOpsCallback cb = mModeWatchers.get(callback);
+            if (cb != null) {
+                try {
+                    mService.stopWatchingMode(cb);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
     public int checkOp(int op, int uid, String packageName) {
         try {
             int mode = mService.checkOperation(op, uid, packageName);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c3321ea..fa2f8c8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -139,6 +139,7 @@
 
     final IWindowSession mWindowSession;
     final Display mDisplay;
+    final String mBasePackageName;
 
     long mLastTrackballTime = 0;
     final TrackballAxis mTrackballAxisX = new TrackballAxis();
@@ -355,6 +356,7 @@
         // allow the spawning of threads.
         mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper());
         mDisplay = display;
+        mBasePackageName = context.getBasePackageName();
 
         CompatibilityInfoHolder cih = display.getCompatibilityInfo();
         mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
@@ -477,6 +479,9 @@
                 mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                 mFallbackEventHandler.setView(view);
                 mWindowAttributes.copyFrom(attrs);
+                if (mWindowAttributes.packageName == null) {
+                    mWindowAttributes.packageName = mBasePackageName;
+                }
                 attrs = mWindowAttributes;
                 // Keep track of the actual window flags supplied by the client.
                 mClientWindowLayoutFlags = attrs.flags;
@@ -774,6 +779,9 @@
             attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
             attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
             mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
+            if (mWindowAttributes.packageName == null) {
+                mWindowAttributes.packageName = mBasePackageName;
+            }
             mWindowAttributes.flags |= compatibleWindowFlag;
 
             applyKeepScreenOnFlag(mWindowAttributes);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index bd28abc..b5d216a 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -460,13 +460,15 @@
     /**
      * Check permissions when adding a window.
      * 
-     * @param attrs The window's LayoutParams. 
+     * @param attrs The window's LayoutParams.
+     * @param outAppOp First element will be filled with the app op corresponding to
+     *                 this window, or OP_NONE.
      *  
      * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
      *      else an error code, usually
      *      {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
      */
-    public int checkAddPermission(WindowManager.LayoutParams attrs);
+    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
 
     /**
      * Check permissions when adding a window.
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
new file mode 100644
index 0000000..4e75a61
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.internal.app;
+
+oneway interface IAppOpsCallback {
+    void opChanged(int op, String packageName);
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index c4f1bc48..83967f6 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -17,6 +17,7 @@
 package com.android.internal.app;
 
 import android.app.AppOpsManager;
+import com.android.internal.app.IAppOpsCallback;
 
 interface IAppOpsService {
     List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
@@ -26,4 +27,6 @@
     int noteOperation(int code, int uid, String packageName);
     int startOperation(int code, int uid, String packageName);
     void finishOperation(int code, int uid, String packageName);
+    void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
+    void stopWatchingMode(IAppOpsCallback callback);
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 8135d22..9d0903c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -17,6 +17,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
 import android.app.ProgressDialog;
 import android.app.SearchManager;
 import android.app.UiModeManager;
@@ -1185,9 +1186,11 @@
 
     /** {@inheritDoc} */
     @Override
-    public int checkAddPermission(WindowManager.LayoutParams attrs) {
+    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
         int type = attrs.type;
-        
+
+        outAppOp[0] = AppOpsManager.OP_NONE;
+
         if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
                 || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
             return WindowManagerGlobal.ADD_OKAY;
@@ -1210,6 +1213,7 @@
             case TYPE_SYSTEM_ERROR:
             case TYPE_SYSTEM_OVERLAY:
                 permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+                outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
                 break;
             default:
                 permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
index 748b3cb..e94d03c 100644
--- a/services/java/com/android/server/AppOpsService.java
+++ b/services/java/com/android/server/AppOpsService.java
@@ -25,6 +25,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 
 import android.app.AppOpsManager;
@@ -34,7 +35,9 @@
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.AtomicFile;
@@ -45,6 +48,7 @@
 import android.util.Xml;
 
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
@@ -82,7 +86,7 @@
     final SparseArray<HashMap<String, Ops>> mUidOps
             = new SparseArray<HashMap<String, Ops>>();
 
-    final static class Ops extends SparseArray<Op> {
+    public final static class Ops extends SparseArray<Op> {
         public final String packageName;
         public final int uid;
 
@@ -92,7 +96,7 @@
         }
     }
 
-    final static class Op {
+    public final static class Op {
         public final int op;
         public int mode;
         public int duration;
@@ -106,6 +110,34 @@
         }
     }
 
+    final SparseArray<ArrayList<Callback>> mOpModeWatchers
+            = new SparseArray<ArrayList<Callback>>();
+    final HashMap<String, ArrayList<Callback>> mPackageModeWatchers
+            = new HashMap<String, ArrayList<Callback>>();
+    final HashMap<IBinder, Callback> mModeWatchers
+            = new HashMap<IBinder, Callback>();
+
+    public final class Callback implements DeathRecipient {
+        final IAppOpsCallback mCallback;
+
+        public Callback(IAppOpsCallback callback) {
+            mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingMode(mCallback);
+        }
+    }
+
     public AppOpsService(File storagePath) {
         mFile = new AtomicFile(storagePath);
         mHandler = new Handler();
@@ -205,15 +237,94 @@
     public void setMode(int code, int uid, String packageName, int mode) {
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
+        ArrayList<Callback> repCbs = null;
+        code = AppOpsManager.opToSwitch(code);
         synchronized (this) {
-            Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, true);
+            Op op = getOpLocked(code, uid, packageName, true);
             if (op != null) {
                 if (op.mode != mode) {
                     op.mode = mode;
+                    ArrayList<Callback> cbs = mOpModeWatchers.get(code);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArrayList<Callback>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    cbs = mPackageModeWatchers.get(packageName);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArrayList<Callback>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
                     scheduleWriteNowLocked();
                 }
             }
         }
+        if (repCbs != null) {
+            for (int i=0; i<repCbs.size(); i++) {
+                try {
+                    repCbs.get(i).mCallback.opChanged(code, packageName);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
+        synchronized (this) {
+            op = AppOpsManager.opToSwitch(op);
+            Callback cb = mModeWatchers.get(callback.asBinder());
+            if (cb == null) {
+                cb = new Callback(callback);
+                mModeWatchers.put(callback.asBinder(), cb);
+            }
+            if (op != AppOpsManager.OP_NONE) {
+                ArrayList<Callback> cbs = mOpModeWatchers.get(op);
+                if (cbs == null) {
+                    cbs = new ArrayList<Callback>();
+                    mOpModeWatchers.put(op, cbs);
+                }
+                cbs.add(cb);
+            }
+            if (packageName != null) {
+                ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
+                if (cbs == null) {
+                    cbs = new ArrayList<Callback>();
+                    mPackageModeWatchers.put(packageName, cbs);
+                }
+                cbs.add(cb);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingMode(IAppOpsCallback callback) {
+        synchronized (this) {
+            Callback cb = mModeWatchers.remove(callback.asBinder());
+            if (cb != null) {
+                cb.unlinkToDeath();
+                for (int i=0; i<mOpModeWatchers.size(); i++) {
+                    ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
+                    cbs.remove(cb);
+                    if (cbs.size() <= 0) {
+                        mOpModeWatchers.removeAt(i);
+                    }
+                }
+                if (mPackageModeWatchers.size() > 0) {
+                    Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator();
+                    while (it.hasNext()) {
+                        ArrayList<Callback> cbs = it.next();
+                        cbs.remove(cb);
+                        if (cbs.size() <= 0) {
+                            it.remove();
+                        }
+                    }
+                }
+            }
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 62f9965..78699fb 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -221,6 +221,16 @@
             mBlacklist.init();
             mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
 
+            // Monitor for app ops mode changes.
+            AppOpsManager.Callback callback = new AppOpsManager.Callback() {
+                public void opChanged(int op, String packageName) {
+                    synchronized (mLock) {
+                        applyAllProviderRequirementsLocked();
+                    }
+                }
+            };
+            mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, callback);
+
             // prepare providers
             loadProvidersLocked();
             updateProvidersLocked();
@@ -1335,6 +1345,17 @@
         }
     }
 
+    private void applyAllProviderRequirementsLocked() {
+        for (LocationProviderInterface p : mProviders) {
+            // If provider is already disabled, don't need to do anything
+            if (!isAllowedBySettingsLocked(p.getName(), UserHandle.getUid(mCurrentUserId, 0))) {
+                continue;
+            }
+
+            applyRequirementsLocked(p.getName());
+        }
+    }
+
     @Override
     public Location getLastLocation(LocationRequest request, String packageName) {
         if (D) Log.d(TAG, "getLastLocation: " + request);
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index 9065525..21d3111 100644
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -466,7 +466,7 @@
         //synchronized (mInputDeviceVibrators) {
         //    return !mInputDeviceVibrators.isEmpty() || vibratorExists();
         //}
-        return true || vibratorExists();
+        return vibratorExists();
     }
 
     private void doVibratorOn(long millis, int uid) {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 1e5cd54..4409762 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -42,6 +42,7 @@
 
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
+import android.app.AppOpsManager;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.policy.impl.PhoneWindowManager;
@@ -311,6 +312,8 @@
 
     final IBatteryStats mBatteryStats;
 
+    final AppOpsManager mAppOps;
+
     /**
      * All currently active sessions with clients.
      */
@@ -765,6 +768,7 @@
 
         mActivityManager = ActivityManagerNative.getDefault();
         mBatteryStats = BatteryStatsService.getService();
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
 
         // Get persisted window scale setting
         mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
@@ -2024,7 +2028,8 @@
     public int addWindow(Session session, IWindow client, int seq,
             WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
             Rect outContentInsets, InputChannel outInputChannel) {
-        int res = mPolicy.checkAddPermission(attrs);
+        int[] appOp = new int[1];
+        int res = mPolicy.checkAddPermission(attrs, appOp);
         if (res != WindowManagerGlobal.ADD_OKAY) {
             return res;
         }
@@ -2128,7 +2133,7 @@
             }
 
             win = new WindowState(this, session, client, token,
-                    attachedWindow, seq, attrs, viewVisibility, displayContent);
+                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
             if (win.mDeathRecipient == null) {
                 // Client has apparently died, so there is no reason to
                 // continue.
@@ -2166,6 +2171,9 @@
             }
             win.attach();
             mWindowMap.put(client.asBinder(), win);
+            if (win.mAppOp != AppOpsManager.OP_NONE) {
+                mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
+            }
 
             if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
                 token.appWindowToken.startingWindow = win;
@@ -2376,6 +2384,9 @@
 
         if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win);
         mWindowMap.remove(win.mClient.asBinder());
+        if (win.mAppOp != AppOpsManager.OP_NONE) {
+            mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
+        }
 
         final WindowList windows = win.getWindowList();
         windows.remove(win);
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index a335958..6648e56 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import android.app.AppOpsManager;
 import com.android.server.input.InputWindowHandle;
 
 import android.content.Context;
@@ -69,6 +70,9 @@
     final Context mContext;
     final Session mSession;
     final IWindow mClient;
+    final int mAppOp;
+    // UserId and appId of the owner. Don't display windows of non-current user.
+    final int mOwnerUid;
     WindowToken mToken;
     WindowToken mRootToken;
     AppWindowToken mAppToken;
@@ -270,18 +274,16 @@
 
     DisplayContent  mDisplayContent;
 
-    // UserId and appId of the owner. Don't display windows of non-current user.
-    int mOwnerUid;
-
     /** When true this window can be displayed on screens owther than mOwnerUid's */
     private boolean mShowToOwnerOnly;
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
-           WindowState attachedWindow, int seq, WindowManager.LayoutParams a,
+           WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, final DisplayContent displayContent) {
         mService = service;
         mSession = s;
         mClient = c;
+        mAppOp = appOp;
         mToken = token;
         mOwnerUid = s.mUid;
         mAttrs.copyFrom(a);
@@ -383,7 +385,7 @@
 
     @Override
     public int getOwningUid() {
-        return mSession.mUid;
+        return mOwnerUid;
     }
 
     @Override
@@ -1129,7 +1131,9 @@
                 pw.print(" mSession="); pw.print(mSession);
                 pw.print(" mClient="); pw.println(mClient.asBinder());
         pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid);
-                pw.print(" mShowToOwnerOnly="); pw.println(mShowToOwnerOnly);
+                pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
+                pw.print(" package="); pw.print(mAttrs.packageName);
+                pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
         pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
         pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
                 pw.print(" h="); pw.print(mRequestedHeight);
@@ -1273,9 +1277,12 @@
 
     @Override
     public String toString() {
-        if (mStringNameCache == null || mLastTitle != mAttrs.getTitle()
-                || mWasExiting != mExiting) {
-            mLastTitle = mAttrs.getTitle();
+        CharSequence title = mAttrs.getTitle();
+        if (title == null || title.length() <= 0) {
+            title = mAttrs.packageName;
+        }
+        if (mStringNameCache == null || mLastTitle != title || mWasExiting != mExiting) {
+            mLastTitle = title;
             mWasExiting = mExiting;
             mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
                     + " u" + UserHandle.getUserId(mSession.mUid)