altosdroid: Fix tab updates on Android 11 after rotate
authorKeith Packard <keithp@keithp.com>
Sat, 16 Oct 2021 22:48:22 +0000 (15:48 -0700)
committerKeith Packard <keithp@keithp.com>
Sat, 16 Oct 2021 23:23:29 +0000 (16:23 -0700)
Android 11 appears to have "optimized" application rotation by
regenerating fragments automatically. This means the tab fragments
aren't getting created by TabsAdapter.getItem, so that code didn't
know about them, which caused it to not know which tab was active so
all of the application state wasn't getting updated in the tabs after
rotation.

Fix this by telling TabsAdapter about fragments that are already
created -- altosdroid hears about them in the registerTab hook.

Signed-off-by: Keith Packard <keithp@keithp.com>
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosDebug.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosDroidTab.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/TabPad.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/TabsAdapter.java

index 469ec50abfd92c33c3e90aacd2aba542511be738..b2bfeb214caf88723dab31e81d34c714030fc545 100644 (file)
@@ -56,11 +56,14 @@ public class AltosDebug {
                Log.e(TAG, String.format(format, arguments));
        }
 
                Log.e(TAG, String.format(format, arguments));
        }
 
+       static void trace(String format, Object ... arguments) {
+               error(format, arguments);
+               for (StackTraceElement el : Thread.currentThread().getStackTrace())
+                       Log.e(TAG, "\t" + el.toString() + "\n");
+       }
+
        static void check_ui(String format, Object ... arguments) {
        static void check_ui(String format, Object ... arguments) {
-               if (Looper.myLooper() == Looper.getMainLooper()) {
-                       Log.e(TAG, String.format("ON UI THREAD " + format, arguments));
-                       for (StackTraceElement el : Thread.currentThread().getStackTrace())
-                               Log.e(TAG, "\t" + el.toString() + "\n");
-               }
+               if (Looper.myLooper() == Looper.getMainLooper())
+                       trace("ON UI THREAD " + format, arguments);
        }
 }
        }
 }
index 1fcb0a470a54b6800af756d12cd845835beb5422..05a023acde3d60f51d27b3caddc1043291336b3c 100644 (file)
@@ -199,15 +199,16 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        };
 
                }
        };
 
-
        private ServiceConnection mConnection = new ServiceConnection() {
                public void onServiceConnected(ComponentName className, IBinder service) {
        private ServiceConnection mConnection = new ServiceConnection() {
                public void onServiceConnected(ComponentName className, IBinder service) {
+                       AltosDebug.debug("onServiceConnected\n");
                        mService = new Messenger(service);
                        try {
                                Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
                                msg.replyTo = mMessenger;
                                mService.send(msg);
                        } catch (RemoteException e) {
                        mService = new Messenger(service);
                        try {
                                Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
                                msg.replyTo = mMessenger;
                                mService.send(msg);
                        } catch (RemoteException e) {
+                               AltosDebug.debug("attempt to register telemetry service client failed\n");
                                // In this case the service has crashed before we could even do anything with it
                        }
                        if (pending_usb_device != null) {
                                // In this case the service has crashed before we could even do anything with it
                        }
                        if (pending_usb_device != null) {
@@ -220,17 +221,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
 
                public void onServiceDisconnected(ComponentName className) {
                }
 
                public void onServiceDisconnected(ComponentName className) {
+                       AltosDebug.debug("onServiceDisconnected\n");
                        // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
                        mService = null;
                }
        };
 
        void doBindService() {
                        // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
                        mService = null;
                }
        };
 
        void doBindService() {
+               AltosDebug.debug("doBindService\n");
                bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
                mIsBound = true;
        }
 
        void doUnbindService() {
                bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
                mIsBound = true;
        }
 
        void doUnbindService() {
+               AltosDebug.debug("doUnbindService\n");
                if (mIsBound) {
                        // If we have received the service, and hence registered with it, then now is the time to unregister.
                        if (mService != null) {
                if (mIsBound) {
                        // If we have received the service, and hence registered with it, then now is the time to unregister.
                        if (mService != null) {
@@ -248,6 +252,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        }
 
                }
        }
 
+       public AltosDroidTab findTab(String name) {
+               for (AltosDroidTab mTab : mTabs)
+                       if (name.equals(mTab.tab_name()))
+                               return mTab;
+               return null;
+       }
+
        public void registerTab(AltosDroidTab mTab) {
                mTabs.add(mTab);
        }
        public void registerTab(AltosDroidTab mTab) {
                mTabs.add(mTab);
        }
@@ -441,6 +452,12 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
        void update_ui(TelemetryState telem_state, AltosState state, boolean quiet) {
 
 
        void update_ui(TelemetryState telem_state, AltosState state, boolean quiet) {
 
+               AltosDebug.debug("update_ui telem %b state %b quiet %b saved_state %b\n",
+                                telem_state != null,
+                                state != null,
+                                quiet,
+                                saved_state != null);
+
                this.state = state;
 
                int prev_state = AltosLib.ao_flight_invalid;
                this.state = state;
 
                int prev_state = AltosLib.ao_flight_invalid;
@@ -530,8 +547,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        saved_state = new SavedState(state);
                }
 
                        saved_state = new SavedState(state);
                }
 
-               for (AltosDroidTab mTab : mTabs)
+               for (AltosDroidTab mTab : mTabs) {
+                       AltosDebug.debug("mTab %s current %s\n",
+                                        mTab, mTabsAdapter.currentItem());
                        mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
                        mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
+               }
 
                if (mAltosVoice != null && mTabsAdapter.currentItem() != null)
                        mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem(), quiet);
 
                if (mAltosVoice != null && mTabsAdapter.currentItem() != null)
                        mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem(), quiet);
@@ -594,13 +614,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
        @Override
        public void onCreate(Bundle savedInstanceState) {
 
        @Override
        public void onCreate(Bundle savedInstanceState) {
+               AltosDebug.init(this);
+               AltosDebug.debug("+++ ON CREATE +++");
+
                // Initialise preferences
                AltosDroidPreferences.init(this);
                setTheme(themes[AltosDroidPreferences.font_size()]);
                super.onCreate(savedInstanceState);
                // Initialise preferences
                AltosDroidPreferences.init(this);
                setTheme(themes[AltosDroidPreferences.font_size()]);
                super.onCreate(savedInstanceState);
-               AltosDebug.init(this);
-               AltosDebug.debug("+++ ON CREATE +++");
-
 
                fm = getSupportFragmentManager();
 
 
                fm = getSupportFragmentManager();
 
@@ -616,10 +636,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
                mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
 
 
                mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
 
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null);
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null);
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null);
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null);
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null, findTab(tab_pad_name));
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null, findTab(tab_flight_name));
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null, findTab(tab_recover_name));
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null, findTab(tab_map_name));
 
                // Display the Version
                mVersion = (TextView) findViewById(R.id.version);
 
                // Display the Version
                mVersion = (TextView) findViewById(R.id.version);
@@ -740,11 +760,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        @Override
        public void onNewIntent(Intent intent) {
                super.onNewIntent(intent);
        @Override
        public void onNewIntent(Intent intent) {
                super.onNewIntent(intent);
-               AltosDebug.debug("onNewIntent");
+               AltosDebug.debug("+ ON NEW INTENT +");
                noticeIntent(intent);
        }
 
                noticeIntent(intent);
        }
 
-       private void enable_location_updates() {
+       private void enable_location_updates(boolean do_update) {
                // Listen for GPS and Network position updates
                LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
 
                // Listen for GPS and Network position updates
                LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
 
@@ -765,7 +785,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        AltosDebug.debug("Failed to get GPS updates\n");
                }
 
                        AltosDebug.debug("Failed to get GPS updates\n");
                }
 
-               update_ui(telemetry_state, state, true);
+               if (do_update)
+                       update_ui(telemetry_state, state, true);
        }
 
        static final int MY_PERMISSION_REQUEST = 1001;
        }
 
        static final int MY_PERMISSION_REQUEST = 1001;
@@ -789,7 +810,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                                        if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
                                                have_location_permission = true;
                                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                                        if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
                                                have_location_permission = true;
-                                               enable_location_updates();
+                                               enable_location_updates(true);
                                                if (map_online != null)
                                                        map_online.position_permission();
                                        }
                                                if (map_online != null)
                                                        map_online.position_permission();
                                        }
@@ -803,9 +824,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
        @Override
        public void onResume() {
 
        @Override
        public void onResume() {
-               super.onResume();
                AltosDebug.debug("+ ON RESUME +");
 
                AltosDebug.debug("+ ON RESUME +");
 
+               super.onResume();
+
                if (!asked_permission) {
                        asked_permission = true;
                        if (ActivityCompat.checkSelfPermission(this,
                if (!asked_permission) {
                        asked_permission = true;
                        if (ActivityCompat.checkSelfPermission(this,
@@ -833,13 +855,15 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        }
                }
                if (have_location_permission)
                        }
                }
                if (have_location_permission)
-                       enable_location_updates();
+                       enable_location_updates(false);
        }
 
        @Override
        public void onPause() {
        }
 
        @Override
        public void onPause() {
-               super.onPause();
                AltosDebug.debug("- ON PAUSE -");
                AltosDebug.debug("- ON PAUSE -");
+
+               super.onPause();
+
                // Stop listening for location updates
                if (have_location_permission)
                        ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
                // Stop listening for location updates
                if (have_location_permission)
                        ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
@@ -847,15 +871,19 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
        @Override
        public void onStop() {
 
        @Override
        public void onStop() {
-               super.onStop();
                AltosDebug.debug("-- ON STOP --");
                AltosDebug.debug("-- ON STOP --");
+
+               super.onStop();
        }
 
        @Override
        public void onDestroy() {
        }
 
        @Override
        public void onDestroy() {
-               super.onDestroy();
                AltosDebug.debug("--- ON DESTROY ---");
 
                AltosDebug.debug("--- ON DESTROY ---");
 
+               super.onDestroy();
+
+               saved_state = null;
+
                doUnbindService();
                if (mAltosVoice != null) {
                        mAltosVoice.stop();
                doUnbindService();
                if (mAltosVoice != null) {
                        mAltosVoice.stop();
index e6923c376643b245f2592a50d1bc75fe7ac0f419..f79c88e6c00b90aad1f8e17318d2f8cf9d4ffa44 100644 (file)
@@ -68,6 +68,7 @@ public abstract class AltosDroidTab extends Fragment implements AltosUnitsListen
 
        @Override
        public void onAttach(Context context) {
 
        @Override
        public void onAttach(Context context) {
+               AltosDebug.debug("tab onAttach %s %s\n", tab_name(), this);
                super.onAttach(context);
                altos_droid = (AltosDroid) context;
                altos_droid.registerTab(this);
                super.onAttach(context);
                altos_droid = (AltosDroid) context;
                altos_droid.registerTab(this);
@@ -75,6 +76,7 @@ public abstract class AltosDroidTab extends Fragment implements AltosUnitsListen
 
        @Override
        public void onDetach() {
 
        @Override
        public void onDetach() {
+               AltosDebug.debug("tab onDetach %s %s\n", tab_name(), this);
                super.onDetach();
                altos_droid.unregisterTab(this);
                altos_droid = null;
                super.onDetach();
                altos_droid.unregisterTab(this);
                altos_droid = null;
@@ -83,13 +85,14 @@ public abstract class AltosDroidTab extends Fragment implements AltosUnitsListen
        @Override
        public void onResume() {
                super.onResume();
        @Override
        public void onResume() {
                super.onResume();
-               AltosDebug.debug("onResume tab %s\n", tab_name());
+               AltosDebug.debug("onResume tab %s %s\n", tab_name(), this);
                set_visible(true);
        }
 
        public void update_ui(TelemetryState telem_state, AltosState state,
                              AltosGreatCircle from_receiver, Location receiver, boolean is_current)
        {
                set_visible(true);
        }
 
        public void update_ui(TelemetryState telem_state, AltosState state,
                              AltosGreatCircle from_receiver, Location receiver, boolean is_current)
        {
+               AltosDebug.debug("update_ui %s is_current %b\n", tab_name(), is_current);
                last_telem_state = telem_state;
                last_state = state;
                last_from_receiver = from_receiver;
                last_telem_state = telem_state;
                last_state = state;
                last_from_receiver = from_receiver;
index 4a8b3f862b9bbeed9077133fc6e8a5b1b1f84f69..fd997612725530357ab9a593b802835d729c632d 100644 (file)
@@ -67,6 +67,7 @@ public class TabPad extends AltosDroidTab {
 
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+               AltosDebug.debug("TabPad onCreateView\n");
                View v = inflater.inflate(R.layout.tab_pad, container, false);
                battery_voltage_view = (TextView) v.findViewById(R.id.battery_voltage_value);
                battery_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled),
                View v = inflater.inflate(R.layout.tab_pad, container, false);
                battery_voltage_view = (TextView) v.findViewById(R.id.battery_voltage_value);
                battery_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled),
@@ -157,12 +158,15 @@ public class TabPad extends AltosDroidTab {
                receiver_latitude_view = (TextView) v.findViewById(R.id.receiver_lat_value);
                receiver_longitude_view = (TextView) v.findViewById(R.id.receiver_lon_value);
                receiver_altitude_view = (TextView) v.findViewById(R.id.receiver_alt_value);
                receiver_latitude_view = (TextView) v.findViewById(R.id.receiver_lat_value);
                receiver_longitude_view = (TextView) v.findViewById(R.id.receiver_lon_value);
                receiver_altitude_view = (TextView) v.findViewById(R.id.receiver_alt_value);
-        return v;
+
+               AltosDebug.debug("TabPad onCreateView done battery_voltage_view %s\n", battery_voltage_view);
+               return v;
        }
 
        public String tab_name() { return AltosDroid.tab_pad_name; }
 
        public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
        }
 
        public String tab_name() { return AltosDroid.tab_pad_name; }
 
        public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+               AltosDebug.debug("pad show state %b bvv %s\n", state != null, battery_voltage_view);
                if (state != null) {
                        battery_voltage_view.setText(AltosDroid.number("%1.2f V", state.battery_voltage));
                        battery_lights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING);
                if (state != null) {
                        battery_voltage_view.setText(AltosDroid.number("%1.2f V", state.battery_voltage));
                        battery_lights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING);
@@ -244,8 +248,11 @@ public class TabPad extends AltosDroidTab {
                        double altitude = AltosLib.MISSING;
                        if (receiver.hasAltitude())
                                altitude = receiver.getAltitude();
                        double altitude = AltosLib.MISSING;
                        if (receiver.hasAltitude())
                                altitude = receiver.getAltitude();
-                       receiver_latitude_view.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
-                       receiver_longitude_view.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
+                       String lat_text = AltosDroid.pos(receiver.getLatitude(), "N", "S");
+                       String lon_text = AltosDroid.pos(receiver.getLongitude(), "E", "W");
+                       AltosDebug.debug("lat %s lon %s\n", lat_text, lon_text);
+                       receiver_latitude_view.setText(lat_text);
+                       receiver_longitude_view.setText(lon_text);
                        set_value(receiver_altitude_view, AltosConvert.height, 1, altitude);
                }
        }
                        set_value(receiver_altitude_view, AltosConvert.height, 1, altitude);
                }
        }
index 23d365b4492e06c136e3f008eaf6304adde8d759..0878c011d7c3a39c19d5d113b854567ab8fcb250 100644 (file)
@@ -54,10 +54,11 @@ public class TabsAdapter extends FragmentPagerAdapter
                private final Bundle args;
                private Fragment fragment;
 
                private final Bundle args;
                private Fragment fragment;
 
-               TabInfo(String _tag, Class<?> _class, Bundle _args) {
+               TabInfo(String _tag, Class<?> _class, Bundle _args, Fragment _fragment) {
                        tag = _tag;
                        clss = _class;
                        args = _args;
                        tag = _tag;
                        clss = _class;
                        args = _args;
+                       fragment = _fragment;
                }
        }
 
                }
        }
 
@@ -86,11 +87,11 @@ public class TabsAdapter extends FragmentPagerAdapter
                mViewPager.addOnPageChangeListener(this);
        }
 
                mViewPager.addOnPageChangeListener(this);
        }
 
-       public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
+       public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args, Fragment fragment) {
                tabSpec.setContent(new DummyTabFactory(mContext));
                String tag = tabSpec.getTag();
 
                tabSpec.setContent(new DummyTabFactory(mContext));
                String tag = tabSpec.getTag();
 
-               TabInfo info = new TabInfo(tag, clss, args);
+               TabInfo info = new TabInfo(tag, clss, args, fragment);
                mTabs.add(info);
                mTabHost.addTab(tabSpec);
                notifyDataSetChanged();
                mTabs.add(info);
                mTabHost.addTab(tabSpec);
                notifyDataSetChanged();
@@ -105,7 +106,8 @@ public class TabsAdapter extends FragmentPagerAdapter
        public Fragment getItem(int position) {
                TabInfo info = mTabs.get(position);
                AltosDebug.debug("TabsAdapter.getItem(%d)", position);
        public Fragment getItem(int position) {
                TabInfo info = mTabs.get(position);
                AltosDebug.debug("TabsAdapter.getItem(%d)", position);
-               info.fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
+               if (info.fragment == null)
+                       info.fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
                return info.fragment;
        }
 
                return info.fragment;
        }
 
@@ -121,15 +123,19 @@ public class TabsAdapter extends FragmentPagerAdapter
 
                AltosDroidTab   cur_frag = (AltosDroidTab) mTabs.get(position).fragment;
 
 
                AltosDroidTab   cur_frag = (AltosDroidTab) mTabs.get(position).fragment;
 
+               AltosDebug.debug("TabsAdapter.onTabChanged(%s) = %d cur %s prev %s", tabId, position, cur_frag, prev_frag);
+
                if (prev_frag != cur_frag) {
                        if (prev_frag != null) {
                                prev_frag.set_visible(false);
                        }
                }
                if (prev_frag != cur_frag) {
                        if (prev_frag != null) {
                                prev_frag.set_visible(false);
                        }
                }
-               if (cur_frag != null) {
+
+               /* This happens when the tab is selected before any of them
+                * have been created, like during rotation
+                */
+               if (cur_frag != null)
                        cur_frag.set_visible(true);
                        cur_frag.set_visible(true);
-               }
-               AltosDebug.debug("TabsAdapter.onTabChanged(%s) = %d", tabId, position);
                mViewPager.setCurrentItem(position);
        }
 
                mViewPager.setCurrentItem(position);
        }